diff --git a/README.md b/README.md index 2e76b59..14c4e39 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,12 @@ Failing to initialize the class before calling any other method will throw a `No ## Documentation -Follow these guides to set up tracking, forms, chat widgets, and Push notifications on your website. +Follow these guides to set up tracking, forms, chat widgets, popups, and Push notifications on your website. - [Understanding Sessions](/docs/sessions.md) - [Tracking Events](/docs/tracking.md) - [Forms](/docs/forms.md) +- [Popups](/docs/popups.md) - [Webchat](/docs/webchat.md) - [WhatsApp widget](/docs/whatsapp.md) - [Push notifications](/docs/push.md) @@ -76,32 +77,42 @@ Hellotext.removeEventListener(eventName, callback) ### Sessions and attribution -| Event | When it fires | Callback payload | -| --- | --- | --- | -| `session-set` | The session value is set, including when an existing session is restored during initialization. | The current session value, also available as `Hellotext.session`. | -| `utm-set` | UTM parameters are saved. | A JSON string containing the saved UTM parameters and `observed_at`. Use `JSON.parse` to read it as an object. | +| Event | When it fires | Callback payload | +| ------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | +| `session-set` | The session value is set, including when an existing session is restored during initialization. | The current session value, also available as `Hellotext.session`. | +| `utm-set` | UTM parameters are saved. | A JSON string containing the saved UTM parameters and `observed_at`. Use `JSON.parse` to read it as an object. | See [Understanding Sessions](/docs/sessions.md) and [Tracking Events](/docs/tracking.md). ### Forms -| Event | When it fires | Callback payload | -| --- | --- | --- | -| `forms:collected` | Forms found on the page have finished loading, before automatic mounting. | The `FormCollection` instance, with methods such as `getById`, `getByIndex`, and `forEach`. | -| `form:completed` | A form completes, or a previously completed form is restored from local storage during mounting. | `{ id, state, data, completedAt }`, where `data` contains the submitted values and `completedAt` is a timestamp in milliseconds. | +| Event | When it fires | Callback payload | +| ----------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | +| `forms:collected` | Forms found on the page have finished loading, before automatic mounting. | The `FormCollection` instance, with methods such as `getById`, `getByIndex`, and `forEach`. | +| `form:completed` | A form completes, or a previously completed form is restored from local storage during mounting. | `{ id, state, data, completedAt }`, where `data` contains the submitted values and `completedAt` is a timestamp in milliseconds. | See [Forms](/docs/forms.md) for collection, mounting, and completion details. +### Popups + +| Event | When it fires | Callback payload | +| --------------- | ------------------------------------------------------------------ | ---------------- | +| `popup:mounted` | A popup is mounted, before its initial display state is evaluated. | None. | +| `popup:opened` | A popup dialog becomes visible. | None. | +| `popup:closed` | A popup is dismissed. | None. | + +See [Popups](/docs/popups.md) for configuration and display behaviour. + ### Smart Alerts Each alert event receives `{ kind }`, where `kind` identifies the section: `homepage`, `product_collection`, or `product_details`. -| Event | When it fires | Callback payload | -| --- | --- | --- | -| `alert:shown` | A section is displayed by a successful `show` call, including forced calls. | `{ kind }` | -| `alert:dismissed` | The visitor clicks the secondary action, hiding the alert and starting its dismissal cooldown. | `{ kind }` | -| `alert:accepted` | The visitor clicks the primary action, before the browser permission result or subscription completion. | `{ kind }` | +| Event | When it fires | Callback payload | +| ----------------- | ------------------------------------------------------------------------------------------------------- | ---------------- | +| `alert:shown` | A section is displayed by a successful `show` call, including forced calls. | `{ kind }` | +| `alert:dismissed` | The visitor clicks the secondary action, hiding the alert and starting its dismissal cooldown. | `{ kind }` | +| `alert:accepted` | The visitor clicks the primary action, before the browser permission result or subscription completion. | `{ kind }` | `alert:accepted` does not confirm that the visitor granted permission or subscribed. Programmatic hiding, cleanup, and dismissals received from another tab do not emit `alert:dismissed`. @@ -116,20 +127,20 @@ See [Smart Alerts](/docs/push.md#show-a-smart-alert) for display options and dis ### Webchat -| Event | When it fires | Callback payload | -| --- | --- | --- | -| `webchat:mounted` | The Webchat widget is mounted. | None. | -| `webchat:opened` | The Webchat conversation opens. | None. | -| `webchat:closed` | The Webchat conversation closes. | None. | -| `webchat:message:sent` | A visitor's message or quick reply is successfully sent. | The message object, including `id`, `body`, and `attachments`, with additional context for quick replies and product cards. | -| `webchat:message:received` | An incoming message is added to the Webchat conversation. | The message object, with `body` containing its displayed text. | +| Event | When it fires | Callback payload | +| -------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| `webchat:mounted` | The Webchat widget is mounted. | None. | +| `webchat:opened` | The Webchat conversation opens. | None. | +| `webchat:closed` | The Webchat conversation closes. | None. | +| `webchat:message:sent` | A visitor's message or quick reply is successfully sent. | The message object, including `id`, `body`, and `attachments`, with additional context for quick replies and product cards. | +| `webchat:message:received` | An incoming message is added to the Webchat conversation. | The message object, with `body` containing its displayed text. | See [Webchat events](/docs/webchat.md#events) for message payload examples. ### Cart -| Event | When it fires | Callback payload | -| --- | --- | --- | +| Event | When it fires | Callback payload | +| ------------ | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `cart.added` | The visitor clicks an add-to-cart button in a Webchat product card. | `{ object_parameters: { items }, source }`. Each item contains `product`, `quantity`, and optional `reference` and `source`; the outer `source` contains `kind`, `message_id`, and `button_id`. | Handle `cart.added` in your storefront integration to update the cart. This event records the button @@ -145,11 +156,12 @@ Hellotext.initialize('HELLOTEXT_BUSINESS_ID', configurationOptions) ### Configuration Options -| Property | Description | Type | Default | -| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----------------------------------------- | -| session | A valid Hellotext session which was stored previously. When not set, Hellotext attempts to retrieve the stored value from `document.cookie` when available, otherwise it creates a new session. | String | null | -| autoGenerateSession | Whether the library should automatically generate a session when no session is found in the query or the cookies | Boolean | true | -| forms | An object that controls how Hellotext should control the forms on the page. See [Forms](/docs/forms.md) documentation for more information. | Object | { autoMount: true, successMessage: true } | -| webchat | An object that overrides the dashboard webchat configuration, or `false` to disable automatic webchat mounting. See [Webchat](/docs/webchat.md). | Object \| false | Dashboard webchat when configured | -| whatsappWidget | An object that overrides the dashboard WhatsApp widget configuration, or `false` to disable automatic WhatsApp widget mounting. | Object \| false | Dashboard WhatsApp widget when configured | -| push | Configure browser Push with a notification worker URL and an optional channel ID, or pass `false` to disable it. See the [Push setup guide](/docs/push.md) for the worker file and button examples. | Object \| false | Enabled when available | +| Property | Description | Type | Default | +| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ----------------------------------------- | +| session | A valid Hellotext session which was stored previously. When not set, Hellotext attempts to retrieve the stored value from `document.cookie` when available, otherwise it creates a new session. | String | null | +| autoGenerateSession | Whether the library should automatically generate a session when no session is found in the query or the cookies | Boolean | true | +| forms | An object that controls how Hellotext should control the forms on the page. See [Forms](/docs/forms.md) documentation for more information. | Object | { autoMount: true, successMessage: true } | +| popup | Options for the dashboard popup, or `false` to disable automatic popup mounting. See [Popups](/docs/popups.md). | Object \| false | Dashboard popup when configured | +| webchat | An object that overrides the dashboard webchat configuration, or `false` to disable automatic webchat mounting. See [Webchat](/docs/webchat.md). | Object \| false | Dashboard webchat when configured | +| whatsappWidget | An object that overrides the dashboard WhatsApp widget configuration, or `false` to disable automatic WhatsApp widget mounting. | Object \| false | Dashboard WhatsApp widget when configured | +| push | Configure browser Push with a notification worker URL and an optional channel ID, or pass `false` to disable it. See the [Push setup guide](/docs/push.md) for the worker file and button examples. | Object \| false | Enabled when available | diff --git a/__tests__/api/popups_test.js b/__tests__/api/popups_test.js new file mode 100644 index 0000000..bd1bfec --- /dev/null +++ b/__tests__/api/popups_test.js @@ -0,0 +1,160 @@ +/** + * @jest-environment jsdom + */ + +import PopupsAPI from '../../src/api/popups' +import Hellotext from '../../src/hellotext' +import { Configuration } from '../../src/core' +import { Locale } from '../../src/core/configuration/locale' + +describe('PopupsAPI', () => { + beforeEach(() => { + Configuration.apiRoot = 'https://api.hellotext.test/v1' + Configuration.popup.device = 'desktop' + Locale._identifier = 'es' + Hellotext.business = { + id: 'business-id', + data: null, + setData: jest.fn(), + setLocale: jest.fn(), + } + + jest.spyOn(Hellotext, 'session', 'get').mockReturnValue('session-123') + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue({ + business: { id: 'business-id' }, + html: '', + locale: 'es', + }), + }) + }) + + afterEach(() => { + jest.restoreAllMocks() + Configuration.apiRoot = 'https://api.hellotext.com/v1' + Configuration.popup.id = undefined + Configuration.popup.container = 'body' + Configuration.popup.device = 'auto' + Locale._identifier = undefined + }) + + it('fetches the public popup with session, locale, and device params', async () => { + const element = await PopupsAPI.get('popup-id') + const url = new URL(global.fetch.mock.calls[0][0]) + + expect(url.pathname).toBe('/v1/public/popups/popup-id') + expect(url.searchParams.get('session')).toBe('session-123') + expect(url.searchParams.get('locale')).toBe('es') + expect(url.searchParams.get('device')).toBe('desktop') + expect(global.fetch.mock.calls[0][1].headers.Authorization).toBe('Bearer business-id') + expect(element.id).toBe('popup-widget') + expect(Hellotext.business.setData).toHaveBeenCalledWith({ id: 'business-id' }) + expect(Hellotext.business.setLocale).toHaveBeenCalledWith('es') + }) + + it('resolves the automatic device from the viewport before requesting markup', async () => { + Configuration.popup.device = 'auto' + Object.defineProperty(window, 'innerWidth', { configurable: true, value: 767 }) + + await PopupsAPI.get('popup-id') + + const url = new URL(global.fetch.mock.calls[0][0]) + expect(url.searchParams.get('device')).toBe('mobile') + }) + + it('returns null when the popup request fails', async () => { + global.fetch.mockResolvedValue({ ok: false }) + + await expect(PopupsAPI.get('popup-id')).resolves.toBeNull() + }) + + it('returns null when the popup request errors', async () => { + global.fetch.mockRejectedValue(new Error('Network error')) + + await expect(PopupsAPI.get('popup-id')).resolves.toBeNull() + }) + + it('returns null when the popup response is invalid JSON', async () => { + global.fetch.mockResolvedValue({ + ok: true, + json: jest.fn().mockRejectedValue(new Error('Invalid JSON')), + }) + + await expect(PopupsAPI.get('popup-id')).resolves.toBeNull() + }) + + it('submits popup data with the current session', async () => { + global.fetch.mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue({ id: 'submission-id' }), + }) + + const response = await PopupsAPI.submit( + 'popup-id', + { + email: 'customer@example.com', + metadata: { fields: { email: 'customer@example.com' } }, + }, + 'submission-attempt-id', + ) + + const request = global.fetch.mock.calls[0] + const body = JSON.parse(request[1].body) + + expect(request[0]).toBe('https://api.hellotext.test/v1/public/popups/popup-id/submissions') + expect(request[1].method).toBe('POST') + expect(request[1].headers.Authorization).toBe('Bearer business-id') + expect(request[1].headers['Idempotency-Key']).toBe('submission-attempt-id') + expect(body).toEqual({ + session: 'session-123', + popup_submission: { + email: 'customer@example.com', + metadata: { + fields: { + email: 'customer@example.com', + }, + }, + }, + }) + expect(response.succeeded).toBe(true) + }) + + it('generates an idempotency key when the caller does not provide one', async () => { + await PopupsAPI.submit('popup-id', {}) + + expect(global.fetch.mock.calls[0][1].headers['Idempotency-Key']).toMatch( + /^[a-zA-Z0-9._:-]+$/, + ) + }) + + it('resends verification through the route stored by the backend', async () => { + const response = await PopupsAPI.resend('popup-id', 'submission-id', 'action-token') + const request = global.fetch.mock.calls[0] + + expect(request[0]).toBe( + 'https://api.hellotext.test/v1/public/popups/popup-id/submissions/submission-id/resend', + ) + expect(request[1]).toEqual({ + method: 'POST', + headers: Hellotext.headers, + body: JSON.stringify({ token: 'action-token' }), + }) + expect(response.succeeded).toBe(true) + }) + + it('cancels the previous submission before changing its destination', async () => { + const response = await PopupsAPI.cancel('popup-id', 'submission-id', 'action-token') + const request = global.fetch.mock.calls[0] + + expect(request[0]).toBe( + 'https://api.hellotext.test/v1/public/popups/popup-id/submissions/submission-id/cancel', + ) + expect(request[1]).toEqual({ + method: 'POST', + headers: Hellotext.headers, + body: JSON.stringify({ token: 'action-token' }), + }) + expect(response.succeeded).toBe(true) + }) +}) diff --git a/__tests__/controllers/popup_controller_test.js b/__tests__/controllers/popup_controller_test.js new file mode 100644 index 0000000..6da4472 --- /dev/null +++ b/__tests__/controllers/popup_controller_test.js @@ -0,0 +1,595 @@ +/** + * @jest-environment jsdom + */ + +import PopupController from '../../src/controllers/popup_controller' +import PopupsAPI from '../../src/api/popups' +import Hellotext from '../../src/hellotext' + +describe('PopupController', () => { + let controller + const buildController = ({ + hasBubble = true, + id = 'popup-id', + } = {}) => { + const element = document.createElement('article') + const bubble = document.createElement('button') + const dialog = document.createElement('section') + const completed = document.createElement('section') + const stepOne = document.createElement('section') + const stepTwo = document.createElement('section') + const emailInput = document.createElement('input') + const phoneInput = document.createElement('input') + const stepOneButton = document.createElement('button') + const stepTwoButton = document.createElement('button') + const globalError = document.createElement('p') + const resendButton = document.createElement('button') + const changeDestinationButton = document.createElement('button') + + bubble.textContent = '10% OFF' + emailInput.type = 'email' + emailInput.required = true + emailInput.dataset.popupFieldKind = 'email' + emailInput.dataset.popupFieldKey = 'email' + emailInput.dataset.popupStepId = 'step-one' + phoneInput.type = 'tel' + phoneInput.required = true + phoneInput.dataset.popupFieldKind = 'phone' + phoneInput.dataset.popupFieldKey = 'phone' + phoneInput.dataset.popupStepId = 'step-two' + stepOne.dataset.stepId = 'step-one' + stepOne.dataset.stepName = 'Step 1' + stepTwo.dataset.stepId = 'step-two' + stepTwo.dataset.stepName = 'Step 2' + stepTwo.hidden = true + completed.hidden = true + completed.innerHTML = [ + '

We sent it to {destination}', + ' via {channel}. It may take a minute to arrive.

', + ].join('') + resendButton.textContent = 'Resend' + resendButton.hidden = true + resendButton.dataset.countdownLabel = 'Resend in %{time}' + changeDestinationButton.hidden = true + changeDestinationButton.dataset.emailLabel = 'Change email' + changeDestinationButton.dataset.phoneLabel = 'Change number' + completed.append(resendButton, changeDestinationButton) + globalError.hidden = true + globalError.dataset.submitError = "We couldn't submit your information. Please try again." + + stepOne.appendChild(emailInput) + stepTwo.appendChild(phoneInput) + dialog.append(stepOne, stepTwo, globalError, completed) + element.append(bubble, dialog) + document.body.appendChild(element) + + controller = new PopupController() + Object.defineProperty(controller, 'element', { + value: element, + writable: false, + configurable: true, + }) + + controller.bubbleTarget = bubble + controller.dialogTarget = dialog + controller.completedTarget = completed + controller.globalErrorTarget = globalError + controller.stepTargets = [stepOne, stepTwo] + controller.inputTargets = [emailInput, phoneInput] + controller.submitButtonTargets = [stepOneButton, stepTwoButton] + Object.defineProperties(controller, { + resendButtonTarget: { value: resendButton, configurable: true }, + changeDestinationButtonTarget: { value: changeDestinationButton, configurable: true }, + hasResendButtonTarget: { value: true, configurable: true }, + hasChangeDestinationButtonTarget: { value: true, configurable: true }, + hasGlobalErrorTarget: { value: true, configurable: true }, + }) + controller.hasBubbleTarget = hasBubble + controller.hasBubbleValue = hasBubble + controller.captureValue = { capture_id: 'capture-id' } + controller.deviceValue = 'all' + controller.idValue = id + controller.initialize() + + return { + element, + bubble, + dialog, + completed, + stepOne, + stepTwo, + emailInput, + phoneInput, + globalError, + resendButton, + changeDestinationButton, + } + } + + beforeEach(() => { + jest.spyOn(PopupsAPI, 'submit').mockResolvedValue({ + failed: false, + json: jest.fn().mockResolvedValue({ + id: 'submission-id', + verification_state: 'unverified', + action_token: 'action-token', + delivery_status: 'queued', + delivery_channel: 'email', + destination: 'customer@example.com', + }), + }) + jest.spyOn(PopupsAPI, 'resend').mockResolvedValue({ + succeeded: true, + data: { headers: new Headers({ 'Retry-After': '60' }), status: 202 }, + }) + jest.spyOn(PopupsAPI, 'cancel').mockResolvedValue({ failed: false, succeeded: true }) + jest.spyOn(Hellotext.eventEmitter, 'dispatch') + }) + + afterEach(() => { + jest.useRealTimers() + jest.restoreAllMocks() + document.body.innerHTML = '' + }) + + it('shows the bubble first and opens the dialog when clicked', () => { + const { element, bubble, dialog } = buildController() + + controller.connect() + + expect(element.hidden).toBe(false) + expect(bubble.hidden).toBe(false) + expect(dialog.hidden).toBe(true) + expect(Hellotext.eventEmitter.dispatch).toHaveBeenCalledTimes(1) + expect(Hellotext.eventEmitter.dispatch).toHaveBeenNthCalledWith(1, 'popup:mounted') + + controller.open() + + expect(bubble.hidden).toBe(true) + expect(dialog.hidden).toBe(false) + expect(Hellotext.eventEmitter.dispatch).toHaveBeenNthCalledWith(2, 'popup:opened') + }) + + it('dispatches popup:mounted before an automatic popup opens and popup:closed when it is dismissed', () => { + const { element, dialog } = buildController({ hasBubble: false }) + + controller.connect() + controller.close() + + expect(element.hidden).toBe(true) + expect(dialog.hidden).toBe(true) + expect(Hellotext.eventEmitter.dispatch).toHaveBeenNthCalledWith(1, 'popup:mounted') + expect(Hellotext.eventEmitter.dispatch).toHaveBeenNthCalledWith(2, 'popup:opened') + expect(Hellotext.eventEmitter.dispatch).toHaveBeenNthCalledWith(3, 'popup:closed') + }) + + it('validates the current step before moving to the next one', async () => { + const { stepOne, stepTwo, emailInput } = buildController({ hasBubble: false }) + + controller.connect() + + await controller.next() + + expect(stepOne.hidden).toBe(false) + expect(stepTwo.hidden).toBe(true) + expect(PopupsAPI.submit).not.toHaveBeenCalled() + + emailInput.value = 'customer@example.com' + + await controller.next() + + expect(stepOne.hidden).toBe(true) + expect(stepTwo.hidden).toBe(false) + expect(PopupsAPI.submit).not.toHaveBeenCalled() + }) + + it('advances instead of submitting when the form submits before the last step', async () => { + const { stepOne, stepTwo, emailInput } = buildController({ hasBubble: false }) + const event = { preventDefault: jest.fn() } + + controller.connect() + emailInput.value = 'customer@example.com' + + await controller.submit(event) + + expect(event.preventDefault).toHaveBeenCalled() + expect(stepOne.hidden).toBe(true) + expect(stepTwo.hidden).toBe(false) + expect(PopupsAPI.submit).not.toHaveBeenCalled() + }) + + it('submits collected fields and shows the completed step on the last step', async () => { + const { completed, emailInput, phoneInput, stepOne, stepTwo } = buildController({ hasBubble: false }) + + controller.connect() + emailInput.value = 'customer@example.com' + await controller.next() + phoneInput.value = '+15551234567' + + await controller.next() + + expect(PopupsAPI.submit).toHaveBeenCalledWith( + 'popup-id', + { + email: 'customer@example.com', + phone: '+15551234567', + metadata: { + capture: { + capture_id: 'capture-id', + }, + fields: { + email: 'customer@example.com', + phone: '+15551234567', + }, + steps: [ + { + id: 'step-one', + name: 'Step 1', + fields: { + email: 'customer@example.com', + }, + }, + { + id: 'step-two', + name: 'Step 2', + fields: { + phone: '+15551234567', + }, + }, + ], + }, + }, + expect.any(String), + ) + expect(stepOne.hidden).toBe(true) + expect(stepTwo.hidden).toBe(true) + expect(completed.hidden).toBe(false) + expect(completed.querySelector('p').textContent).toBe( + 'We sent it to customer@example.com via email. It may take a minute to arrive.', + ) + expect(completed.querySelector('strong').textContent).toBe('customer@example.com') + }) + + it('reuses the idempotency key after a lost response and restores the submit buttons', async () => { + const { emailInput, phoneInput, globalError } = buildController({ hasBubble: false }) + PopupsAPI.submit.mockRejectedValueOnce(new TypeError('Failed to fetch')) + + controller.connect() + emailInput.value = 'customer@example.com' + await controller.next() + phoneInput.value = '+15551234567' + + await controller.submit() + const firstKey = PopupsAPI.submit.mock.calls[0][2] + + expect(controller.submitButtonTargets.every(button => !button.disabled)).toBe(true) + expect(globalError.hidden).toBe(false) + expect(globalError.textContent).toBe("We couldn't submit your information. Please try again.") + + await controller.submit() + + expect(PopupsAPI.submit.mock.calls[1][2]).toBe(firstKey) + expect(globalError.hidden).toBe(true) + }) + + it('generates a new idempotency key after the submitted data changes', async () => { + const { emailInput, phoneInput } = buildController({ hasBubble: false }) + PopupsAPI.submit.mockRejectedValueOnce(new TypeError('Failed to fetch')) + + controller.connect() + emailInput.value = 'customer@example.com' + await controller.next() + phoneInput.value = '+15551234567' + await controller.submit() + const firstKey = PopupsAPI.submit.mock.calls[0][2] + + phoneInput.value = '+15557654321' + await controller.submit() + + expect(PopupsAPI.submit.mock.calls[1][2]).not.toBe(firstKey) + }) + + it('shows submission errors that are not associated with an input', async () => { + const { emailInput, phoneInput, globalError } = buildController({ hasBubble: false }) + PopupsAPI.submit.mockResolvedValueOnce({ + failed: true, + json: jest.fn().mockResolvedValue({ + errors: [{ parameter: 'base', description: 'Enter an email address or phone number.' }], + }), + }) + + controller.connect() + emailInput.value = 'customer@example.com' + await controller.next() + phoneInput.value = '+15551234567' + await controller.submit() + + expect(globalError.hidden).toBe(false) + expect(globalError.textContent).toBe('Enter an email address or phone number.') + expect(controller.submitButtonTargets.every(button => !button.disabled)).toBe(true) + }) + + it('shows a one-minute resend cooldown and the change action for the submitted identity', async () => { + jest.useFakeTimers() + jest.setSystemTime(new Date('2026-08-24T12:00:00Z')) + const { emailInput, phoneInput, resendButton, changeDestinationButton } = buildController({ hasBubble: false }) + + controller.connect() + phoneInput.required = false + emailInput.value = 'customer@example.com' + await controller.next() + await controller.submit() + + expect(resendButton.hidden).toBe(false) + expect(resendButton.disabled).toBe(true) + expect(resendButton.textContent).toBe('Resend in 1:00') + expect(changeDestinationButton.hidden).toBe(false) + expect(changeDestinationButton.textContent).toBe('Change email') + + jest.advanceTimersByTime(60000) + + expect(resendButton.disabled).toBe(false) + expect(resendButton.textContent).toBe('Resend') + }) + + it('resends only the identity shown in the completed step and restarts the cooldown', async () => { + jest.useFakeTimers() + jest.setSystemTime(new Date('2026-08-24T12:00:00Z')) + const { emailInput, phoneInput, resendButton } = buildController({ hasBubble: false }) + + controller.connect() + phoneInput.required = false + emailInput.value = 'customer@example.com' + await controller.next() + await controller.submit() + jest.advanceTimersByTime(60000) + + await controller.resend({ preventDefault: jest.fn() }) + + expect(PopupsAPI.resend).toHaveBeenCalledWith( + 'popup-id', + 'submission-id', + 'action-token', + ) + expect(resendButton.disabled).toBe(true) + expect(resendButton.textContent).toBe('Resend in 1:00') + }) + + it('returns to and focuses the step that owns the completed identity', async () => { + const { completed, emailInput, phoneInput, stepOne, changeDestinationButton } = buildController({ hasBubble: false }) + jest.spyOn(emailInput, 'focus') + + controller.connect() + phoneInput.required = false + emailInput.value = 'customer@example.com' + await controller.next() + await controller.submit() + await controller.changeDestination({ preventDefault: jest.fn() }) + + expect(PopupsAPI.cancel).toHaveBeenCalledWith( + 'popup-id', + 'submission-id', + 'action-token', + ) + expect(completed.hidden).toBe(true) + expect(stepOne.hidden).toBe(false) + expect(emailInput.focus).toHaveBeenCalled() + expect(changeDestinationButton.textContent).toBe('Change email') + expect(controller.submissionDeliveryStatus).toBeNull() + expect(controller.submissionDeliveryChannel).toBeNull() + expect(controller.submissionDestination).toBeNull() + }) + + it('keeps the completed step visible when the previous submission cannot be canceled', async () => { + const { completed, emailInput, phoneInput, stepOne, changeDestinationButton } = buildController({ hasBubble: false }) + PopupsAPI.cancel.mockResolvedValueOnce({ failed: true, succeeded: false }) + + controller.connect() + phoneInput.required = false + emailInput.value = 'customer@example.com' + await controller.next() + await controller.submit() + await controller.changeDestination({ preventDefault: jest.fn() }) + + expect(completed.hidden).toBe(false) + expect(stepOne.hidden).toBe(true) + expect(changeDestinationButton.disabled).toBe(false) + expect(controller.submissionId).toBe('submission-id') + }) + + it('keeps the completed step visible when cancellation cannot reach the API', async () => { + const { completed, emailInput, phoneInput, stepOne, changeDestinationButton } = buildController({ hasBubble: false }) + PopupsAPI.cancel.mockRejectedValueOnce(new TypeError('Failed to fetch')) + + controller.connect() + phoneInput.required = false + emailInput.value = 'customer@example.com' + await controller.next() + await controller.submit() + await controller.changeDestination({ preventDefault: jest.fn() }) + + expect(completed.hidden).toBe(false) + expect(stepOne.hidden).toBe(true) + expect(changeDestinationButton.disabled).toBe(false) + expect(controller.submissionId).toBe('submission-id') + }) + + it('returns to the identity selected by the backend fallback route', async () => { + const { emailInput, phoneInput, stepOne } = buildController({ hasBubble: false }) + jest.spyOn(emailInput, 'focus') + controller.inputTargets = [phoneInput, emailInput] + controller.submissionId = 'submission-id' + controller.submissionActionToken = 'action-token' + controller.submissionDeliveryChannel = 'email' + controller.submissionDestination = 'customer@example.com' + emailInput.value = 'customer@example.com' + phoneInput.value = '+15551234567' + + controller.showCompleted() + await controller.changeDestination({ preventDefault: jest.fn() }) + + expect(stepOne.hidden).toBe(false) + expect(emailInput.focus).toHaveBeenCalled() + }) + + it('uses a readable channel when the popup only requires one identity field', () => { + const { completed, emailInput, phoneInput } = buildController({ hasBubble: false }) + + phoneInput.required = false + emailInput.value = 'customer@example.com' + + controller.showCompleted() + + expect(completed.querySelector('p').textContent).toBe( + 'We sent it to customer@example.com via email. It may take a minute to arrive.', + ) + + emailInput.value = 'updated@example.com' + controller.showCompleted() + + expect(completed.querySelector('p').textContent).toBe( + 'We sent it to updated@example.com via email. It may take a minute to arrive.', + ) + }) + + it('falls back to the first populated optional identity field', () => { + const { completed, emailInput, phoneInput } = buildController({ hasBubble: false }) + + emailInput.required = false + phoneInput.required = false + emailInput.value = 'customer@example.com' + + controller.showCompleted() + + expect(completed.querySelector('p').textContent).toBe( + 'We sent it to customer@example.com via email. It may take a minute to arrive.', + ) + }) + + it('formats a required phone with the popup country prefix', () => { + const { completed, emailInput, phoneInput } = buildController({ hasBubble: false }) + + emailInput.required = false + phoneInput.dataset.popupPhonePrefix = '+58' + phoneInput.value = '04126625353' + + controller.showCompleted() + + expect(completed.querySelector('p').textContent).toBe( + 'We sent it to +584126625353 via phone. It may take a minute to arrive.', + ) + }) + + it('uses the backend delivery channel and destination in the completed step', () => { + const { completed, emailInput, phoneInput } = buildController({ hasBubble: false }) + + emailInput.value = 'customer@example.com' + phoneInput.value = '+15551234567' + controller.submissionDeliveryChannel = 'sms' + controller.submissionDestination = '+15551234567' + + controller.showCompleted() + + expect(completed.querySelector('p').textContent).toBe( + 'We sent it to +15551234567 via sms. It may take a minute to arrive.', + ) + }) + + // The server renders both completion variants as targets; the controller only chooses + // which one is visible. This mirrors that markup. + const renderCompletionCopy = completed => { + const deliveryHeadline = document.createElement('header') + const deliveryDescription = document.createElement('div') + const noDeliveryHeadline = document.createElement('header') + const noDeliveryDescription = document.createElement('div') + const actions = document.createElement('footer') + + deliveryHeadline.className = 'hellotext--popup__completion-headline' + deliveryHeadline.innerHTML = '

Your code is on its way

' + deliveryDescription.className = 'hellotext--popup__completion-description' + deliveryDescription.textContent = 'We sent it to {destination}.' + noDeliveryHeadline.className = 'hellotext--popup__completion-headline' + noDeliveryHeadline.hidden = true + noDeliveryHeadline.innerHTML = '

Thanks for signing up

' + noDeliveryDescription.className = 'hellotext--popup__completion-description' + noDeliveryDescription.hidden = true + noDeliveryDescription.textContent = 'Your details were saved.' + actions.dataset.deliveryActions = '' + completed.append(deliveryHeadline, deliveryDescription, noDeliveryHeadline, noDeliveryDescription, actions) + + Object.defineProperties(controller, { + deliveryCopyTargets: { value: [deliveryHeadline, deliveryDescription], configurable: true }, + noDeliveryCopyTargets: { value: [noDeliveryHeadline, noDeliveryDescription], configurable: true }, + hasDeliveryCopyTarget: { value: true, configurable: true }, + hasNoDeliveryCopyTarget: { value: true, configurable: true }, + }) + + return { deliveryHeadline, deliveryDescription, noDeliveryHeadline, noDeliveryDescription, actions } + } + + it('reveals the server-rendered no-delivery copy and hides delivery actions when delivery is not required', () => { + const { completed, emailInput, resendButton, changeDestinationButton } = buildController({ hasBubble: false }) + const copy = renderCompletionCopy(completed) + + emailInput.value = 'customer@example.com' + controller.submissionDeliveryStatus = 'not_required' + + controller.showCompleted() + + expect(copy.noDeliveryHeadline.hidden).toBe(false) + expect(copy.noDeliveryDescription.hidden).toBe(false) + expect(copy.deliveryHeadline.hidden).toBe(true) + expect(copy.deliveryDescription.hidden).toBe(true) + expect(copy.actions.hidden).toBe(true) + expect(resendButton.hidden).toBe(true) + expect(changeDestinationButton.hidden).toBe(true) + }) + + it('leaves the server markup untouched when revealing the no-delivery copy', () => { + const { completed, emailInput } = buildController({ hasBubble: false }) + const copy = renderCompletionCopy(completed) + + emailInput.value = 'customer@example.com' + controller.submissionDeliveryStatus = 'not_required' + + controller.showCompleted() + + expect(copy.noDeliveryHeadline.innerHTML).toBe('

Thanks for signing up

') + expect(copy.deliveryHeadline.innerHTML).toBe('

Your code is on its way

') + expect(copy.noDeliveryDescription.textContent).toBe('Your details were saved.') + expect(copy.deliveryDescription.textContent).toBe('We sent it to customer@example.com.') + expect(completed.querySelectorAll('h4')).toHaveLength(2) + }) + + it('keeps the delivery copy visible and interpolated when a delivery is queued', () => { + const { completed, emailInput } = buildController({ hasBubble: false }) + const copy = renderCompletionCopy(completed) + + emailInput.value = 'customer@example.com' + controller.submissionDeliveryStatus = 'queued' + + controller.showCompleted() + + expect(copy.deliveryHeadline.hidden).toBe(false) + expect(copy.deliveryDescription.hidden).toBe(false) + expect(copy.deliveryDescription.textContent).toContain('customer@example.com') + expect(copy.noDeliveryHeadline.hidden).toBe(true) + expect(copy.noDeliveryDescription.hidden).toBe(true) + }) + + it('validates the last step before submitting', async () => { + const { completed, emailInput, phoneInput, stepTwo } = buildController({ hasBubble: false }) + + controller.connect() + emailInput.value = 'customer@example.com' + await controller.next() + + await controller.submit() + + expect(PopupsAPI.submit).not.toHaveBeenCalled() + expect(stepTwo.hidden).toBe(false) + expect(completed.hidden).toBe(true) + expect(phoneInput.checkValidity()).toBe(false) + }) + +}) diff --git a/__tests__/core/configuration_test.js b/__tests__/core/configuration_test.js index c60acc3..256e719 100644 --- a/__tests__/core/configuration_test.js +++ b/__tests__/core/configuration_test.js @@ -65,6 +65,28 @@ describe('Configuration', () => { }) }) + describe('.popup', () => { + afterEach(() => { + Configuration.popup.id = undefined + Configuration.popup.container = 'body' + Configuration.popup.device = 'auto' + }) + + it('can be modified', () => { + Configuration.assign({ popup: { id: 'popup-id', container: '#popup-root', device: 'desktop' } }) + + expect(Configuration.popup.id).toEqual('popup-id') + expect(Configuration.popup.container).toEqual('#popup-root') + expect(Configuration.popup.device).toEqual('desktop') + }) + + it('accepts false as an opt-out value', () => { + expect(() => { + Configuration.assign({ popup: false }) + }).not.toThrow() + }) + }) + describe('.locale', () => { beforeEach(() => { Locale._identifier = undefined diff --git a/__tests__/core/event_test.js b/__tests__/core/event_test.js index 83c37fc..bc90603 100644 --- a/__tests__/core/event_test.js +++ b/__tests__/core/event_test.js @@ -9,6 +9,12 @@ describe(".valid", function () { expect(Event.valid("cart.added")).toEqual(true) }); + it("is true for popup lifecycle events", () => { + expect(Event.valid("popup:mounted")).toEqual(true) + expect(Event.valid("popup:opened")).toEqual(true) + expect(Event.valid("popup:closed")).toEqual(true) + }); + it("is false when event name is not defined", () => { expect(Event.valid("undefined-event")).toEqual(false) }); diff --git a/__tests__/hellotext_test.js b/__tests__/hellotext_test.js index c907959..512f7fe 100644 --- a/__tests__/hellotext_test.js +++ b/__tests__/hellotext_test.js @@ -1,7 +1,7 @@ import Hellotext from "../src/hellotext"; import API from "../src/api"; import { Configuration } from "../src/core"; -import { Push, Session, Webchat, WhatsAppWidget } from "../src/models"; +import { Popup, Push, Session, Webchat, WhatsAppWidget } from "../src/models"; const getCookieValue = name => document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop() @@ -15,6 +15,7 @@ const defaultBusiness = (overrides = {}) => ({ features: {}, locale: "en", style_url: "https://example.com/hellotext.css", + popup: null, webchat: null, whitelist: "disabled", ...overrides, @@ -47,17 +48,23 @@ describe("when trying to call methods before initializing the class", () => { }) describe("when initializing business metadata", () => { + let loadPopup let loadWebchat let loadWhatsAppWidget beforeEach(() => { + loadPopup = jest.spyOn(Popup, 'load').mockResolvedValue({}) loadWebchat = jest.spyOn(Webchat, 'load').mockResolvedValue({}) loadWhatsAppWidget = jest.spyOn(WhatsAppWidget, 'load').mockResolvedValue({}) }) afterEach(() => { + loadPopup.mockRestore() loadWebchat.mockRestore() loadWhatsAppWidget.mockRestore() + Configuration.popup.id = undefined + Configuration.popup.container = 'body' + Configuration.popup.device = 'auto' Configuration.webchat.behaviour = null Configuration.webchat.behaviourOverride = false Configuration.webchat.appearance = {} @@ -68,6 +75,7 @@ describe("when initializing business metadata", () => { Configuration.whatsapp.appearance = {} Configuration.whatsapp.number = null Configuration.whatsapp.body = null + Hellotext.popup = undefined }) it("fetches public business data by default and stores it", async () => { @@ -290,6 +298,121 @@ describe("when initializing business metadata", () => { expect(loadWhatsAppWidget).not.toHaveBeenCalled() }) + it("loads the dashboard popup when no explicit popup config is passed", async () => { + const popup = { id: 'dashboard-popup' } + loadPopup.mockResolvedValueOnce(popup) + mockBusinessFetch(defaultBusiness({ popup: { id: 'dashboard-popup' } })) + + await Hellotext.initialize('xy76ks') + + expect(loadPopup).toHaveBeenCalledWith( + 'dashboard-popup', + expect.objectContaining({ container: 'body', shouldMount: expect.any(Function) }), + ) + expect(Hellotext.popup).toEqual(popup) + }) + + it('does not load a popup when the dashboard has no popup id', async () => { + mockBusinessFetch(defaultBusiness({ popup: {} })) + + await Hellotext.initialize('xy76ks') + + expect(loadPopup).not.toHaveBeenCalled() + expect(Hellotext.popup).toBeUndefined() + }) + + it('starts webchat, WhatsApp, and popup loading without waiting for another surface', async () => { + let resolveWebchat + let resolveWhatsApp + let resolvePopup + + loadWebchat.mockImplementation( + () => new Promise(resolve => { + resolveWebchat = resolve + }), + ) + loadWhatsAppWidget.mockImplementation( + () => new Promise(resolve => { + resolveWhatsApp = resolve + }), + ) + loadPopup.mockImplementation( + () => new Promise(resolve => { + resolvePopup = resolve + }), + ) + mockBusinessFetch( + defaultBusiness({ + webchat: { id: 'dashboard-webchat' }, + whatsapp: { id: 'dashboard-whatsapp' }, + popup: { id: 'dashboard-popup' }, + }), + ) + + const initialized = Hellotext.initialize('xy76ks') + await new Promise(resolve => setTimeout(resolve, 0)) + + expect(loadWebchat).toHaveBeenCalledWith('dashboard-webchat') + expect(loadWhatsAppWidget).toHaveBeenCalledWith('dashboard-whatsapp') + expect(loadPopup).toHaveBeenCalledWith( + 'dashboard-popup', + expect.objectContaining({ container: 'body', shouldMount: expect.any(Function) }), + ) + + resolveWebchat({}) + resolveWhatsApp({}) + resolvePopup({}) + await initialized + }) + + it("uses the dashboard popup id with explicit local options", async () => { + mockBusinessFetch(defaultBusiness({ popup: { id: 'dashboard-popup' } })) + + await Hellotext.initialize('xy76ks', { + popup: { + container: '#popup-container', + device: 'desktop', + }, + }) + + expect(loadPopup).toHaveBeenCalledWith( + 'dashboard-popup', + expect.objectContaining({ container: '#popup-container', shouldMount: expect.any(Function) }), + ) + expect(Configuration.popup.container).toEqual('#popup-container') + expect(Configuration.popup.device).toEqual('desktop') + }) + + it("lets an explicit popup id override the dashboard popup id", async () => { + mockBusinessFetch(defaultBusiness({ popup: { id: 'dashboard-popup' } })) + + await Hellotext.initialize('xy76ks', { popup: { id: 'explicit-popup' } }) + + expect(loadPopup).toHaveBeenCalledWith( + 'explicit-popup', + expect.objectContaining({ container: 'body', shouldMount: expect.any(Function) }), + ) + }) + + it("skips popup loading when popup is false", async () => { + mockBusinessFetch(defaultBusiness({ popup: { id: 'dashboard-popup' } })) + + await Hellotext.initialize('xy76ks', { popup: false }) + + expect(loadPopup).not.toHaveBeenCalled() + }) + + it('unmounts the previous popup when a later initialization disables it', async () => { + const unmount = jest.fn() + Hellotext.popup = { unmount } + mockBusinessFetch(defaultBusiness()) + + await Hellotext.initialize('xy76ks', { popup: false }) + + expect(unmount).toHaveBeenCalledTimes(1) + expect(Hellotext.popup).toBeUndefined() + }) + it("does not break initialization when business fetch rejects", async () => { API.businesses.get = jest.fn().mockRejectedValue(new Error("network error")) diff --git a/__tests__/integration/popup_completion_test.js b/__tests__/integration/popup_completion_test.js new file mode 100644 index 0000000..26b8bb8 --- /dev/null +++ b/__tests__/integration/popup_completion_test.js @@ -0,0 +1,86 @@ +/** + * @jest-environment jsdom + */ + +import { Application } from '@hotwired/stimulus' + +import API from '../../src/api' +import PopupController from '../../src/controllers/popup_controller' +import { Configuration } from '../../src/core' +import { Popup } from '../../src/models' + +describe('server-rendered popup completion', () => { + let application + + const popupRuntimeHTML = ` +
+
+
+ +
+ + +
+
+ ` + + beforeEach(() => { + document.body.innerHTML = '
' + Configuration.popup.container = '#popup-container' + jest.spyOn(API.popups, 'get').mockResolvedValue( + new DOMParser().parseFromString(popupRuntimeHTML, 'text/html').querySelector('article'), + ) + + application = Application.start() + application.register('hellotext--popup', PopupController) + }) + + afterEach(() => { + application.stop() + jest.restoreAllMocks() + document.body.innerHTML = '' + Configuration.popup.container = 'body' + }) + + it('mounts Rails completion markup and reveals its thank-you targets without rebuilding HTML', async () => { + await Popup.load('popup-id') + await Promise.resolve() + await Promise.resolve() + + const popup = document.querySelector('.hellotext--popup') + const controller = application.getControllerForElementAndIdentifier(popup, 'hellotext--popup') + const deliveryCopy = popup.querySelectorAll('[data-hellotext--popup-target="deliveryCopy"]') + const noDeliveryCopy = popup.querySelectorAll('[data-hellotext--popup-target="noDeliveryCopy"]') + + expect(controller).toBeInstanceOf(PopupController) + expect(deliveryCopy).toHaveLength(2) + expect(noDeliveryCopy).toHaveLength(2) + + controller.submissionDeliveryStatus = 'not_required' + controller.showCompleted() + + expect([...deliveryCopy].every(element => element.hidden)).toBe(true) + expect([...noDeliveryCopy].every(element => !element.hidden)).toBe(true) + expect(noDeliveryCopy[0].innerHTML).toBe('

Thanks for signing up

') + expect(popup.querySelector('[data-delivery-actions]').hidden).toBe(true) + }) +}) diff --git a/__tests__/models/popup_test.js b/__tests__/models/popup_test.js new file mode 100644 index 0000000..eb9a4f2 --- /dev/null +++ b/__tests__/models/popup_test.js @@ -0,0 +1,104 @@ +/** + * @jest-environment jsdom + */ + +import API from '../../src/api' +import { Configuration } from '../../src/core' +import { Popup } from '../../src/models' + +describe('Popup', () => { + beforeEach(() => { + document.body.innerHTML = '
' + Configuration.popup.container = '#popup-container' + jest.spyOn(API.popups, 'get') + }) + + afterEach(() => { + jest.restoreAllMocks() + document.body.innerHTML = '' + document.querySelectorAll('link[rel="stylesheet"]').forEach(link => { + link.dispatchEvent(new Event('error')) + link.remove() + }) + Configuration.popup.container = 'body' + }) + + it.each(['missing', 'loading', 'failed'])('mounts immediately when the stylesheet is %s', async state => { + if (state !== 'missing') { + const linkTag = document.createElement('link') + linkTag.rel = 'stylesheet' + linkTag.href = 'https://example.com/hellotext.css' + linkTag.setAttribute('data-hellotext-stylesheet', 'true') + if (state === 'failed') linkTag.dataset.hellotextStylesheetLoaded = 'false' + document.head.appendChild(linkTag) + } + + const article = document.createElement('article') + article.className = 'hellotext--popup' + API.popups.get.mockResolvedValue(article) + + const popup = await Popup.load('popup-id') + + expect(document.querySelector('#popup-container article')).toBe(article) + expect(popup.mounted).toBe(true) + await expect(popup.rendered).resolves.toBe(true) + }) + + it('does not mount when the API returns no popup HTML', () => { + API.popups.get.mockResolvedValue(null) + + return Popup.load('popup-id').then(popup => { + return popup.rendered.then(() => { + expect(document.querySelector('#popup-container').children.length).toBe(0) + expect(popup.mounted).toBe(false) + }) + }) + }) + + it('does not mount when the configured container is missing', () => { + Configuration.popup.container = '#missing-container' + jest.spyOn(console, 'warn').mockImplementation(() => {}) + API.popups.get.mockResolvedValue(document.createElement('article')) + + return Popup.load('popup-id').then(popup => { + return popup.rendered.then(() => { + expect(popup.mounted).toBe(false) + expect(console.warn).toHaveBeenCalledWith('Hellotext popup was not mounted because the container #missing-container was not found.') + }) + }) + }) + + it('does not mount when the configured container selector is invalid', () => { + Configuration.popup.container = '[' + jest.spyOn(console, 'warn').mockImplementation(() => {}) + API.popups.get.mockResolvedValue(document.createElement('article')) + + return Popup.load('popup-id').then(popup => { + return popup.rendered.then(() => { + expect(popup.mounted).toBe(false) + expect(console.warn).toHaveBeenCalledWith('Hellotext popup was not mounted because the container [ was not found.') + }) + }) + }) + + it('does not mount after a newer initialization supersedes the request', async () => { + const article = document.createElement('article') + API.popups.get.mockResolvedValue(article) + + const popup = await Popup.load('popup-id', { shouldMount: () => false }) + + expect(document.querySelector('#popup-container article')).toBeNull() + expect(popup.mounted).toBe(false) + }) + + it('unmounts its rendered surface', async () => { + const article = document.createElement('article') + API.popups.get.mockResolvedValue(article) + + const popup = await Popup.load('popup-id') + popup.unmount() + + expect(document.querySelector('#popup-container article')).toBeNull() + expect(popup.mounted).toBe(false) + }) +}) diff --git a/dist/hellotext.js b/dist/hellotext.js index 713aef8..89668db 100644 --- a/dist/hellotext.js +++ b/dist/hellotext.js @@ -1,2 +1,2 @@ /*! For license information please see hellotext.js.LICENSE.txt */ -!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()}(Object("undefined"!=typeof self?self:this),()=>(()=>{"use strict";var e={891(e,t,s){s.d(t,{lg:()=>G,xI:()=>ne});class i{constructor(e,t,s){this.eventTarget=e,this.eventName=t,this.eventOptions=s,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 s=e.index,i=t.index;return si?1:0})}}class n{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,s={}){this.application.handleError(e,`Error ${t}`,s)}clearEventListenersForBinding(e){const t=this.fetchEventListenerForBinding(e);t.hasBindings()||(t.disconnect(),this.removeMappedEventListenerFor(e))}removeMappedEventListenerFor(e){const{eventTarget:t,eventName:s,eventOptions:i}=e,n=this.fetchEventListenerMapForEventTarget(t),r=this.cacheKey(s,i);n.delete(r),0==n.size&&this.eventListenerMaps.delete(t)}fetchEventListenerForBinding(e){const{eventTarget:t,eventName:s,eventOptions:i}=e;return this.fetchEventListener(t,s,i)}fetchEventListener(e,t,s){const i=this.fetchEventListenerMapForEventTarget(e),n=this.cacheKey(t,s);let r=i.get(n);return r||(r=this.createEventListener(e,t,s),i.set(n,r)),r}createEventListener(e,t,s){const n=new i(e,t,s);return this.started&&n.connect(),n}fetchEventListenerMapForEventTarget(e){let t=this.eventListenerMaps.get(e);return t||(t=new Map,this.eventListenerMaps.set(e,t)),t}cacheKey(e,t){const s=[e];return Object.keys(t).sort().forEach(e=>{s.push(`${t[e]?"":"!"}${e}`)}),s.join(":")}}const r={stop:({event:e,value:t})=>(t&&e.stopPropagation(),!0),prevent:({event:e,value:t})=>(t&&e.preventDefault(),!0),self:({event:e,value:t,element:s})=>!t||s===e.target},a=/^(?:(?:([^.]+?)\+)?(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/;function o(e){return e.replace(/(?:[_-])([a-z0-9])/g,(e,t)=>t.toUpperCase())}function c(e){return o(e.replace(/--/g,"-").replace(/__/g,"_"))}function l(e){return e.charAt(0).toUpperCase()+e.slice(1)}function h(e){return e.replace(/([A-Z])/g,(e,t)=>`-${t.toLowerCase()}`)}function u(e){return null!=e}function d(e,t){return Object.prototype.hasOwnProperty.call(e,t)}const p=["meta","ctrl","alt","shift"];class m{constructor(e,t,s,i){this.element=e,this.index=t,this.eventTarget=s.eventTarget||e,this.eventName=s.eventName||function(e){const t=e.tagName.toLowerCase();if(t in g)return g[t](e)}(e)||f("missing event name"),this.eventOptions=s.eventOptions||{},this.identifier=s.identifier||f("missing identifier"),this.methodName=s.methodName||f("missing method name"),this.keyFilter=s.keyFilter||"",this.schema=i}static forToken(e,t){return new this(e.element,e.index,function(e){const t=e.trim().match(a)||[];let s=t[2],i=t[3];return i&&!["keydown","keyup","keypress"].includes(s)&&(s+=`.${i}`,i=""),{eventTarget:(n=t[4],"window"==n?window:"document"==n?document:void 0),eventName:s,eventOptions:t[7]?(r=t[7],r.split(":").reduce((e,t)=>Object.assign(e,{[t.replace(/^!/,"")]:!/^!/.test(t)}),{})):{},identifier:t[5],methodName:t[6],keyFilter:t[1]||i};var n,r}(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 s=t.filter(e=>!p.includes(e))[0];return!!s&&(d(this.keyMappings,s)||f(`contains unknown key filter: ${this.keyFilter}`),this.keyMappings[s].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:s,value:i}of Array.from(this.element.attributes)){const n=s.match(t),r=n&&n[1];r&&(e[o(r)]=b(i))}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[s,i,n,r]=p.map(e=>t.includes(e));return e.metaKey!==s||e.ctrlKey!==i||e.altKey!==n||e.shiftKey!==r}}const g={a:()=>"click",button:()=>"click",form:()=>"submit",details:()=>"toggle",input:e=>"submit"==e.getAttribute("type")?"click":"input",select:()=>"change",textarea:()=>"input"};function f(e){throw new Error(e)}function b(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:s}=this.context.application,{controller:i}=this.context;let n=!0;for(const[r,a]of Object.entries(this.eventOptions))if(r in s){const o=s[r];n=n&&o({name:r,value:a,event:e,element:t,controller:i})}return n}prepareActionEvent(e){return Object.assign(e,{params:this.action.params})}invokeWithEvent(e){const{target:t,currentTarget:s}=e;try{this.method.call(this.controller,e),this.context.logDebugActivity(this.methodName,{event:e,target:t,currentTarget:s,action:this.methodName})}catch(t){const{identifier:s,controller:i,element:n,index:r}=this,a={identifier:s,controller:i,element:n,index:r,event:e};this.context.handleError(t,`invoking action "${this.action}"`,a)}}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 v{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 s of this.matchElementsInTree(e))t.call(this,s)}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,s){this.attributeName=t,this.delegate=s,this.elementObserver=new v(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]:[],s=Array.from(e.querySelectorAll(this.selector));return t.concat(s)}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 T(e,t){let s=e.get(t);return s||(s=new Set,e.set(t,s)),s}class S{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,s){T(e,t).add(s)}(this.valuesByKey,e,t)}delete(e,t){!function(e,t,s){T(e,t).delete(s),function(e,t){const s=e.get(t);null!=s&&0==s.size&&e.delete(t)}(e,t)}(this.valuesByKey,e,t)}has(e,t){const s=this.valuesByKey.get(e);return null!=s&&s.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,s])=>s.has(e)).map(([e,t])=>e)}}class C{constructor(e,t,s,i){this._selector=t,this.details=i,this.elementObserver=new v(e,this),this.delegate=s,this.matchesByElement=new S}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 s=e.matches(t);return this.delegate.selectorMatchElement?s&&this.delegate.selectorMatchElement(e,this.details):s}return!1}matchElementsInTree(e){const{selector:t}=this;if(t){const s=this.matchElement(e)?[e]:[],i=Array.from(e.querySelectorAll(t)).filter(e=>this.matchElement(e));return s.concat(i)}return[]}elementMatched(e){const{selector:t}=this;t&&this.selectorMatched(e,t)}elementUnmatched(e){const t=this.matchesByElement.getKeysForValue(e);for(const s of t)this.selectorUnmatched(e,s)}elementAttributeChanged(e,t){const{selector:s}=this;if(s){const t=this.matchElement(e),i=this.matchesByElement.has(s,e);t&&!i?this.selectorMatched(e,s):!t&&i&&this.selectorUnmatched(e,s)}}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 O{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 s=this.delegate.getStringMapKeyForAttribute(e);if(null!=s){this.stringMap.has(e)||this.stringMapKeyAdded(s,e);const i=this.element.getAttribute(e);if(this.stringMap.get(e)!=i&&this.stringMapValueChanged(i,s,t),null==i){const t=this.stringMap.get(e);this.stringMap.delete(e),t&&this.stringMapKeyRemoved(s,e,t)}else this.stringMap.set(e,i)}}stringMapKeyAdded(e,t){this.delegate.stringMapKeyAdded&&this.delegate.stringMapKeyAdded(e,t)}stringMapValueChanged(e,t,s){this.delegate.stringMapValueChanged&&this.delegate.stringMapValueChanged(e,t,s)}stringMapKeyRemoved(e,t,s){this.delegate.stringMapKeyRemoved&&this.delegate.stringMapKeyRemoved(e,t,s)}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 A{constructor(e,t,s){this.attributeObserver=new w(e,t,this),this.delegate=s,this.tokensByElement=new S}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,s]=this.refreshTokensForElement(e);this.tokensUnmatched(t),this.tokensMatched(s)}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),s=this.readTokensForElement(e),i=function(e,t){const s=Math.max(e.length,t.length);return Array.from({length:s},(s,i)=>[e[i],t[i]])}(t,s).findIndex(([e,t])=>{return i=t,!((s=e)&&i&&s.index==i.index&&s.content==i.content);var s,i});return-1==i?[[],[]]:[t.slice(i),s.slice(i)]}readTokensForElement(e){const t=this.attributeName;return function(e,t,s){return e.trim().split(/\s+/).filter(e=>e.length).map((e,i)=>({element:t,attributeName:s,content:e,index:i}))}(e.getAttribute(t)||"",e,t)}}class E{constructor(e,t,s){this.tokenListObserver=new A(e,t,this),this.delegate=s,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:s}=this.fetchParseResultForToken(e);s&&(this.fetchValuesByTokenForElement(t).set(e,s),this.delegate.elementMatchedValue(t,s))}tokenUnmatched(e){const{element:t}=e,{value:s}=this.fetchParseResultForToken(e);s&&(this.fetchValuesByTokenForElement(t).delete(e),this.delegate.elementUnmatchedValue(t,s))}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 x{constructor(e,t){this.context=e,this.delegate=t,this.bindingsByAction=new Map}start(){this.valueListObserver||(this.valueListObserver=new E(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=m.forToken(e,this.schema);if(t.identifier==this.identifier)return t}elementMatchedValue(e,t){this.connectAction(t)}elementUnmatchedValue(e,t){this.disconnectAction(t)}}class M{constructor(e,t){this.context=e,this.receiver=t,this.stringMapObserver=new O(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 s=this.valueDescriptorMap[t];this.hasValue(e)||this.invokeChangedCallback(e,s.writer(this.receiver[e]),s.writer(s.defaultValue))}stringMapValueChanged(e,t,s){const i=this.valueDescriptorNameMap[t];null!==e&&(null===s&&(s=i.writer(i.defaultValue)),this.invokeChangedCallback(t,e,s))}stringMapKeyRemoved(e,t,s){const i=this.valueDescriptorNameMap[e];this.hasValue(e)?this.invokeChangedCallback(e,i.writer(this.receiver[e]),s):this.invokeChangedCallback(e,i.writer(i.defaultValue),s)}invokeChangedCallbacksForDefaultValues(){for(const{key:e,name:t,defaultValue:s,writer:i}of this.valueDescriptors)null==s||this.controller.data.has(e)||this.invokeChangedCallback(t,i(s),void 0)}invokeChangedCallback(e,t,s){const i=`${e}Changed`,n=this.receiver[i];if("function"==typeof n){const i=this.valueDescriptorNameMap[e];try{const e=i.reader(t);let r=s;s&&(r=i.reader(s)),n.call(this.receiver,e,r)}catch(e){throw e instanceof TypeError&&(e.message=`Stimulus Value "${this.context.identifier}.${i.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 s=this.valueDescriptorMap[t];e[s.name]=s}),e}hasValue(e){const t=`has${l(this.valueDescriptorNameMap[e].name)}`;return this.receiver[t]}}class k{constructor(e,t){this.context=e,this.delegate=t,this.targetsByName=new S}start(){this.tokenListObserver||(this.tokenListObserver=new A(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 s;this.targetsByName.has(t,e)||(this.targetsByName.add(t,e),null===(s=this.tokenListObserver)||void 0===s||s.pause(()=>this.delegate.targetConnected(e,t)))}disconnectTarget(e,t){var s;this.targetsByName.has(t,e)&&(this.targetsByName.delete(t,e),null===(s=this.tokenListObserver)||void 0===s||s.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 I(e,t){const s=L(e);return Array.from(s.reduce((e,s)=>(function(e,t){const s=e[t];return Array.isArray(s)?s:[]}(s,t).forEach(t=>e.add(t)),e),new Set))}function L(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 S,this.outletElementsByName=new S,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:s}){const i=this.getOutlet(e,s);i&&this.connectOutlet(i,e,s)}selectorUnmatched(e,t,{outletName:s}){const i=this.getOutletFromMap(e,s);i&&this.disconnectOutlet(i,e,s)}selectorMatchElement(e,{outletName:t}){const s=this.selector(t),i=this.hasOutlet(e,t),n=e.matches(`[${this.schema.controllerAttribute}~=${t}]`);return!!s&&i&&n&&e.matches(s)}elementMatchedAttribute(e,t){const s=this.getOutletNameFromOutletAttributeName(t);s&&this.updateSelectorObserverForOutlet(s)}elementAttributeValueChanged(e,t){const s=this.getOutletNameFromOutletAttributeName(t);s&&this.updateSelectorObserverForOutlet(s)}elementUnmatchedAttribute(e,t){const s=this.getOutletNameFromOutletAttributeName(t);s&&this.updateSelectorObserverForOutlet(s)}connectOutlet(e,t,s){var i;this.outletElementsByName.has(s,t)||(this.outletsByName.add(s,e),this.outletElementsByName.add(s,t),null===(i=this.selectorObserverMap.get(s))||void 0===i||i.pause(()=>this.delegate.outletConnected(e,t,s)))}disconnectOutlet(e,t,s){var i;this.outletElementsByName.has(s,t)&&(this.outletsByName.delete(s,e),this.outletElementsByName.delete(s,t),null===(i=this.selectorObserverMap.get(s))||void 0===i||i.pause(()=>this.delegate.outletDisconnected(e,t,s)))}disconnectAllOutlets(){for(const e of this.outletElementsByName.keys)for(const t of this.outletElementsByName.getValuesForKey(e))for(const s of this.outletsByName.getValuesForKey(e))this.disconnectOutlet(s,t,e)}updateSelectorObserverForOutlet(e){const t=this.selectorObserverMap.get(e);t&&(t.selector=this.selector(e))}setupSelectorObserverForOutlet(e){const t=this.selector(e),s=new C(document.body,t,this,{outletName:e});this.selectorObserverMap.set(e,s),s.start()}setupAttributeObserverForOutlet(e){const t=this.attributeNameForOutletName(e),s=new w(this.scope.element,t,this);this.attributeObserverMap.set(e,s),s.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 S;return this.router.modules.forEach(t=>{I(t.definition.controllerConstructor,"outlets").forEach(s=>e.add(s,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 N{constructor(e,t){this.logDebugActivity=(e,t={})=>{const{identifier:s,controller:i,element:n}=this;t=Object.assign({identifier:s,controller:i,element:n},t),this.application.logDebugActivity(this.identifier,e,t)},this.module=e,this.scope=t,this.controller=new e.controllerConstructor(this),this.bindingObserver=new x(this,this.dispatcher),this.valueObserver=new M(this,this.controller),this.targetObserver=new k(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,s={}){const{identifier:i,controller:n,element:r}=this;s=Object.assign({identifier:i,controller:n,element:r},s),this.application.handleError(e,`Error ${t}`,s)}targetConnected(e,t){this.invokeControllerMethod(`${t}TargetConnected`,e)}targetDisconnected(e,t){this.invokeControllerMethod(`${t}TargetDisconnected`,e)}outletConnected(e,t,s){this.invokeControllerMethod(`${c(s)}OutletConnected`,e,t)}outletDisconnected(e,t,s){this.invokeControllerMethod(`${c(s)}OutletDisconnected`,e,t)}invokeControllerMethod(e,...t){const s=this.controller;"function"==typeof s[e]&&s[e](...t)}}const P="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 R{constructor(e,t){this.application=e,this.definition=function(e){return{identifier:e.identifier,controllerConstructor:(t=e.controllerConstructor,function(e,t){const s=F(e),i=function(e,t){return P(t).reduce((s,i)=>{const n=function(e,t,s){const i=Object.getOwnPropertyDescriptor(e,s);if(!i||!("value"in i)){const e=Object.getOwnPropertyDescriptor(t,s).value;return i&&(e.get=i.get||e.get,e.set=i.set||e.set),e}}(e,t,i);return n&&Object.assign(s,{[i]:n}),s},{})}(e.prototype,t);return Object.defineProperties(s.prototype,i),s}(t,function(e){return I(e,"blessings").reduce((t,s)=>{const i=s(e);for(const e in i){const s=t[e]||{};t[e]=Object.assign(s,i[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 N(this,e),this.contextsByScope.set(e,t)),t}}class D{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 j{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 s=this.getAttributeNameForKey(e);return this.element.setAttribute(s,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}-${h(e)}`}}class B{constructor(e){this.warnedKeysByObject=new WeakMap,this.logger=e}warn(e,t,s){let i=this.warnedKeysByObject.get(e);i||(i=new Set,this.warnedKeysByObject.set(e,i)),i.has(t)||(i.add(t),this.logger.warn(s,e))}}function $(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 $(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 $(this.schema.targetAttribute,t)}deprecate(e,t){if(e){const{identifier:s}=this,i=this.schema.targetAttribute,n=this.schema.targetAttributeForScope(s);this.guide.warn(e,`target:${t}`,`Please replace ${i}="${s}.${t}" with ${n}="${t}". The ${i} attribute is deprecated and will be removed in a future version of Stimulus.`)}return e}get guide(){return this.scope.guide}}class q{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(s=>this.matchesElement(s,e,t))[0]}findAllElements(e,t){return this.scope.queryElements(e).filter(s=>this.matchesElement(s,e,t))}matchesElement(e,t,s){const i=e.getAttribute(this.scope.schema.controllerAttribute)||"";return e.matches(t)&&i.split(" ").includes(s)}}class U{constructor(e,t,s,i){this.targets=new V(this),this.classes=new D(this),this.data=new j(this),this.containsElement=e=>e.closest(this.controllerSelector)===this.element,this.schema=e,this.element=t,this.identifier=s,this.guide=new B(i),this.outlets=new q(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 $(this.schema.controllerAttribute,this.identifier)}get isDocumentScope(){return this.element===document.documentElement}get documentScope(){return this.isDocumentScope?this:new U(this.schema,document.documentElement,this.identifier,this.guide.logger)}}class z{constructor(e,t,s){this.element=e,this.schema=t,this.delegate=s,this.valueListObserver=new E(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:s}=e;return this.parseValueForElementAndIdentifier(t,s)}parseValueForElementAndIdentifier(e,t){const s=this.fetchScopesByIdentifierForElement(e);let i=s.get(t);return i||(i=this.delegate.createScopeForElementAndIdentifier(e,t),s.set(t,i)),i}elementMatchedValue(e,t){const s=(this.scopeReferenceCounts.get(t)||0)+1;this.scopeReferenceCounts.set(t,s),1==s&&this.delegate.scopeConnected(t)}elementUnmatchedValue(e,t){const s=this.scopeReferenceCounts.get(t);s&&(this.scopeReferenceCounts.set(t,s-1),1==s&&this.delegate.scopeDisconnected(t))}fetchScopesByIdentifierForElement(e){let t=this.scopesByIdentifierByElement.get(e);return t||(t=new Map,this.scopesByIdentifierByElement.set(e,t)),t}}class W{constructor(e){this.application=e,this.scopeObserver=new z(this.element,this.schema,this),this.scopesByIdentifier=new S,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 R(this.application,e);this.connectModule(t);const s=e.controllerConstructor.afterLoad;s&&s.call(e.controllerConstructor,e.identifier,this.application)}unloadIdentifier(e){const t=this.modulesByIdentifier.get(e);t&&this.disconnectModule(t)}getContextForElementAndIdentifier(e,t){const s=this.modulesByIdentifier.get(t);if(s)return s.contexts.find(t=>t.element==e)}proposeToConnectScopeForElementAndIdentifier(e,t){const s=this.scopeObserver.parseValueForElementAndIdentifier(e,t);s?this.scopeObserver.elementMatchedValue(s.element,s):console.error(`Couldn't find or create scope for identifier: "${t}" and element:`,e)}handleError(e,t,s){this.application.handleError(e,t,s)}createScopeForElementAndIdentifier(e,t){return new U(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 H={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"},K("abcdefghijklmnopqrstuvwxyz".split("").map(e=>[e,e]))),K("0123456789".split("").map(e=>[e,e])))};function K(e){return e.reduce((e,[t,s])=>Object.assign(Object.assign({},e),{[t]:s}),{})}class G{constructor(e=document.documentElement,t=H){this.logger=console,this.debug=!1,this.logDebugActivity=(e,t,s={})=>{this.debug&&this.logFormattedMessage(e,t,s)},this.element=e,this.schema=t,this.dispatcher=new n(this),this.router=new W(this),this.actionDescriptorFilters=Object.assign({},r)}static start(e,t){const s=new this(e,t);return s.start(),s}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 s=this.router.getContextForElementAndIdentifier(e,t);return s?s.controller:null}handleError(e,t,s){var i;this.logger.error("%s\n\n%o\n\n%o",t,e,s),null===(i=window.onerror)||void 0===i||i.call(window,t,"",0,0,e)}logFormattedMessage(e,t,s={}){s=Object.assign({application:this},s),this.logger.groupCollapsed(`${e} #${t}`),this.logger.log("details:",Object.assign({},s)),this.logger.groupEnd()}}function J(e,t,s){return e.application.getControllerForElementAndIdentifier(t,s)}function Y(e,t,s){let i=J(e,t,s);return i||(e.application.router.proposeToConnectScopeForElementAndIdentifier(t,s),i=J(e,t,s),i||void 0)}function Z([e,t],s){return function(e){const{token:t,typeDefinition:s}=e,i=`${h(t)}-value`,n=function(e){const{controller:t,token:s,typeDefinition:i}=e,n=function(e){const{controller:t,token:s,typeObject:i}=e,n=u(i.type),r=u(i.default),a=n&&r,o=n&&!r,c=!n&&r,l=X(i.type),h=Q(e.typeObject.default);if(o)return l;if(c)return h;if(l!==h)throw new Error(`The specified default value for the Stimulus Value "${t?`${t}.${s}`:s}" must match the defined type "${l}". The provided default value of "${i.default}" is of type "${h}".`);return a?l:void 0}({controller:t,token:s,typeObject:i}),r=Q(i),a=X(i),o=n||r||a;if(o)return o;throw new Error(`Unknown value type "${t?`${t}.${i}`:s}" for "${s}" value`)}(e);return{type:n,key:i,name:o(i),get defaultValue(){return function(e){const t=X(e);if(t)return ee[t];const s=d(e,"default"),i=d(e,"type"),n=e;if(s)return n.default;if(i){const{type:e}=n,t=X(e);if(t)return ee[t]}return e}(s)},get hasCustomDefaultValue(){return void 0!==Q(s)},reader:te[n],writer:se[n]||se.default}}({controller:s,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 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},se={default:function(e){return`${e}`},array:ie,object:ie};function ie(e){return JSON.stringify(e)}class ne{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:s={},prefix:i=this.identifier,bubbles:n=!0,cancelable:r=!0}={}){const a=new CustomEvent(i?`${i}:${e}`:e,{detail:s,bubbles:n,cancelable:r});return t.dispatchEvent(a),a}}ne.blessings=[function(e){return I(e,"classes").reduce((e,t)=>{return Object.assign(e,(s=t,{[`${s}Class`]:{get(){const{classes:e}=this;if(e.has(s))return e.get(s);{const t=e.getAttributeName(s);throw new Error(`Missing attribute "${t}"`)}}},[`${s}Classes`]:{get(){return this.classes.getAll(s)}},[`has${l(s)}Class`]:{get(){return this.classes.has(s)}}}));var s},{})},function(e){return I(e,"targets").reduce((e,t)=>{return Object.assign(e,(s=t,{[`${s}Target`]:{get(){const e=this.targets.find(s);if(e)return e;throw new Error(`Missing target element "${s}" for "${this.identifier}" controller`)}},[`${s}Targets`]:{get(){return this.targets.findAll(s)}},[`has${l(s)}Target`]:{get(){return this.targets.has(s)}}}));var s},{})},function(e){const t=function(e,t){return L(e).reduce((e,s)=>(e.push(...function(e,t){const s=e[t];return s?Object.keys(s).map(e=>[e,s[e]]):[]}(s,t)),e),[])}(e,"values"),s={valueDescriptorMap:{get(){return t.reduce((e,t)=>{const s=Z(t,this.identifier),i=this.data.getAttributeNameForKey(s.key);return Object.assign(e,{[i]:s})},{})}}};return t.reduce((e,t)=>Object.assign(e,function(e){const t=Z(e,void 0),{key:s,name:i,reader:n,writer:r}=t;return{[i]:{get(){const e=this.data.get(s);return null!==e?n(e):t.defaultValue},set(e){void 0===e?this.data.delete(s):this.data.set(s,r(e))}},[`has${l(i)}`]:{get(){return this.data.has(s)||t.hasCustomDefaultValue}}}}(t)),s)},function(e){return I(e,"outlets").reduce((e,t)=>Object.assign(e,function(e){const t=c(e);return{[`${t}Outlet`]:{get(){const t=this.outlets.find(e),s=this.outlets.getSelectorForOutletName(e);if(t){const s=Y(this,t,e);if(s)return s;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 "${s}".`)}},[`${t}Outlets`]:{get(){const t=this.outlets.findAll(e);return t.length>0?t.map(t=>{const s=Y(this,t,e);if(s)return s;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),s=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 "${s}".`)}},[`${t}OutletElements`]:{get(){return this.outlets.findAll(e)}},[`has${l(t)}Outlet`]:{get(){return this.outlets.has(e)}}}}(t)),{})}],ne.targets=[],ne.outlets=[],ne.values={}},434(e,t,s){s.d(t,{default:()=>ri});var i=s(891);class n extends Error{constructor(e){super(`${e} is not valid. Please provide a valid event name`),this.name="InvalidEvent"}}class r{static events=["session-set","utm-set","forms:collected","form:completed","alert:shown","alert:dismissed","alert:accepted","webchat:mounted","webchat:opened","webchat:closed","webchat:message:sent","webchat:message:received","cart.added"];static valid(e){return r.exists(e)}static invalid(e){return!this.valid(e)}static exists(e){return void 0!==this.events.find(t=>t===e)}constructor(){this.subscribers={}}addSubscriber(e,t){if(r.invalid(e))throw new n(e);this.subscribers={...this.subscribers,[e]:this.subscribers[e]?[...this.subscribers[e],t]:[t]}}removeSubscriber(e,t){if(r.invalid(e))throw new n(e);this.subscribers[e]&&(this.subscribers[e]=this.subscribers[e].filter(e=>e!==t))}dispatch(e,t){this.subscribers[e]?.forEach(e=>{e(t)})}get listeners(){return 0!==Object.keys(this.subscribers).length}}class a{static autoMount=!0;static successMessage=!0;static assign(e){return e&&Object.entries(e).forEach(([e,t])=>{this[e]=t}),this}static get shouldShowSuccessMessage(){return this.successMessage}}class o{static _identifier;static set identifier(e){this._identifier=e}static get identifier(){return this._identifier?this._identifier:this.#e||this.#t||this.#s||"en"}static toString(){return this.identifier}static get#e(){return document?.documentElement?.lang}static get#t(){return document?.querySelector('meta[name="locale"]')?.content}static get#s(){return navigator?.language?.split("-")[0]}}const c={TOP_LEFT:"top-left",TOP_RIGHT:"top-right",BOTTOM_LEFT:"bottom-left",BOTTOM_RIGHT:"bottom-right"},l={ABSOLUTE:"absolute",FIXED:"fixed"},h={MODAL:"modal",POPOVER:"popover"};class u{static _id;static _container="body";static _placement="bottom-right";static _style={};static _appearance={};static _whatsapp={};static _mode=h.POPOVER;static _behaviour=null;static _hasBehaviourOverride=!1;static _strategy=null;static set container(e){this._container=e}static get container(){return this._container}static set placement(e){if(!Object.values(c).includes(e))throw new Error(`Invalid placement value: ${e}`);this._placement=e}static get placement(){return this._placement}static set id(e){this._id=e}static get id(){return this._id}static get isSet(){return!!this._id}static get style(){return this._style}static set style(e){if("object"!=typeof e)throw new Error("Style must be an object");Object.entries(e).forEach(([e,t])=>{if(!["primaryColor","secondaryColor","typography"].includes(e))throw new Error(`Invalid style property: ${e}`);if("typography"!==e&&!this.isHexOrRgba(t))throw new Error(`Invalid color value: ${t} for ${e}. Colors must be hex or rgb/a.`)}),this._style=e}static get appearance(){return this._appearance}static set appearance(e){if(!this.isPlainObject(e))throw new Error("Appearance must be an object");Object.entries(e).forEach(([e,t])=>{if(!["header","launcher"].includes(e))throw new Error(`Invalid appearance property: ${e}`);if(!this.isPlainObject(t))throw new Error(`Appearance ${e} must be an object`);Object.entries(t).forEach(([t,s])=>{if("header"===e&&"name"!==t)throw new Error(`Invalid appearance header property: ${t}`);if("launcher"===e&&"iconUrl"!==t)throw new Error(`Invalid appearance launcher property: ${t}`);if(null!=s&&"string"!=typeof s)throw new Error(`Invalid appearance ${e}.${t} value: ${s}`)})}),this._appearance=e}static get whatsapp(){return this._whatsapp}static set whatsapp(e){if(!this.isPlainObject(e))throw new Error("WhatsApp must be an object");Object.entries(e).forEach(([e,t])=>{if(!["number","restrictToChannel"].includes(e))throw new Error(`Invalid WhatsApp property: ${e}`);if(null!=t){if("number"===e&&"string"!=typeof t)throw new Error(`Invalid WhatsApp number value: ${t}`);if("restrictToChannel"===e&&"boolean"!=typeof t)throw new Error(`Invalid WhatsApp restrictToChannel value: ${t}`)}}),this._whatsapp=e}static get mode(){return this._mode}static set mode(e){if(!Object.values(h).includes(e))throw new Error(`Invalid mode value: ${e}`);this._mode=e}static get behaviour(){return this._behaviour}static set behaviour(e){if(null!=e){if("object"!=typeof e||Array.isArray(e))throw new Error(`Invalid behaviour value: ${e}`);this._behaviour=e}else this._behaviour=e}static get hasBehaviourOverride(){return this._hasBehaviourOverride}static set behaviourOverride(e){this._hasBehaviourOverride=!!e}static get strategy(){return this._strategy?this._strategy:"body"==this.container?l.FIXED:l.ABSOLUTE}static set strategy(e){if(e&&!Object.values(l).includes(e))throw new Error(`Invalid strategy value: ${e}`);this._strategy=e}static assign(e){return e&&Object.entries(e).forEach(([e,t])=>{this[e]=t}),this}static isHexOrRgba(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)}static isPlainObject(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}}const d={TOP_LEFT:"top-left",TOP_RIGHT:"top-right",BOTTOM_LEFT:"bottom-left",BOTTOM_RIGHT:"bottom-right"};class p{static _id;static _container="body";static _placement="bottom-right";static _appearance={};static _number=null;static _body=null;static set id(e){this._id=e}static get id(){return this._id}static set container(e){this._container=e}static get container(){return this._container}static set placement(e){if(!Object.values(d).includes(e))throw new Error(`Invalid placement value: ${e}`);this._placement=e}static get placement(){return this._placement}static get appearance(){return this._appearance}static set appearance(e){if(!this.isPlainObject(e))throw new Error("Appearance must be an object");Object.entries(e).forEach(([e,t])=>{if("launcher"!==e)throw new Error(`Invalid appearance property: ${e}`);if(!this.isPlainObject(t))throw new Error(`Appearance ${e} must be an object`);Object.entries(t).forEach(([t,s])=>{if("iconUrl"!==t)throw new Error(`Invalid appearance launcher property: ${t}`);if(null!=s&&"string"!=typeof s)throw new Error(`Invalid appearance ${e}.${t} value: ${s}`)})}),this._appearance=e}static get number(){return this._number}static set number(e){if(null!=e&&"string"!=typeof e)throw new Error(`Invalid number value: ${e}`);this._number=e}static get body(){return this._body}static set body(e){if(null!=e&&"string"!=typeof e)throw new Error(`Invalid body value: ${e}`);this._body=e}static assign(e){return e&&Object.entries(e).forEach(([e,t])=>{this[e]=t}),this}static isPlainObject(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}}class m{static serviceWorkerUrl=null;static channelId=null;static assign(e){return this.serviceWorkerUrl=e?.serviceWorkerUrl||null,this.channelId=e?.channelId||null,this}}class g{static apiRoot="https://api.hellotext.com/v1";static actionCableUrl="wss://www.hellotext.com/cable";static autoGenerateSession=!0;static session=null;static forms=a;static webchat=u;static whatsapp=p;static push=m;static assign(e){if(e){const t=Object.prototype.hasOwnProperty.call(e,"apiRoot")&&!Object.prototype.hasOwnProperty.call(e,"actionCableUrl");Object.entries(e).forEach(([e,t])=>{"forms"===e?this.forms=a.assign(t):"webchat"===e?this.webchat=u.assign(t):"whatsappWidget"===e?this.whatsapp=p.assign(t):"push"===e?this.push=m.assign(t):this[e]=t}),t&&(this.actionCableUrl=this.actionCableUrlForApiRoot(this.apiRoot))}return this}static set locale(e){o.identifier=e}static get locale(){return o.toString()}static endpoint(e){return`${this.apiRoot}/${e}`}static actionCableUrlForApiRoot(e){try{const t=new URL(e),s="https:"===t.protocol?"wss:":"ws:";return t.protocol=s,t.pathname="/cable",t.search="",t.hash="",t.toString()}catch(e){return this.actionCableUrl}}}const f=class{static get endpoint(){return g.endpoint("public/businesses")}static async get(e){return fetch(`${this.endpoint}/${e}`,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json","Content-Type":"application/json"}})}};class b{static get inPreviewMode(){return(new this).inPreviewMode}constructor(){this.urlSearchParams=new URLSearchParams(window.location.search)}get(e){return this.urlSearchParams.get(this.toHellotextParam(e))}has(e){return this.urlSearchParams.has(this.toHellotextParam(e))}get inPreviewMode(){return this.has("preview")}get session(){return this.get("session")}toHellotextParam(e){return`hello_${e}`}}class y{#i;constructor(e,t){this.response=t,this.#i=e}get data(){return this.response}async json(){return await this.response.json()}get failed(){return!1===this.#i}get succeeded(){return!0===this.#i}}class v{static get endpoint(){return g.endpoint("track/events")}static async create({headers:e,body:t,keepalive:s=!1}){if(b.inPreviewMode)return new y(!0,{received:!0});const i={method:"POST",headers:e,body:JSON.stringify(t)};s&&(i.keepalive=!0);const n=await fetch(this.endpoint,i);return new y(200===n.status,await n.json())}}class w{static get endpoint(){return g.endpoint("public/forms")}static async get(e){const t=new URL(`${this.endpoint}/${e}`);return t.searchParams.append("session",bt.session),t.searchParams.append("locale",o.toString()),fetch(t,{method:"GET",headers:bt.headers})}static async submit(e,t){const s=await fetch(`${this.endpoint}/${e}/submissions`,{method:"POST",headers:bt.headers,body:JSON.stringify({session:bt.session,...t})});return new y(s.ok,s)}}const T=class{static get endpoint(){return g.endpoint("public/identifications")}static async create(e={}){const t=await fetch(this.endpoint,{method:"POST",headers:bt.headers,body:JSON.stringify({session:bt.session,...e})});return new y(t.ok,t)}},S=class{static get endpoint(){return g.endpoint("public/webchats")}static async get(e){const t=new URL(`${this.endpoint}/${e}`);t.searchParams.append("session",bt.session),t.searchParams.append("locale",o.toString()),Object.entries(g.webchat.style).forEach(([e,s])=>{t.searchParams.append(`style[${e}]`,s)}),this.appendWebchatOverrides(t),t.searchParams.append("placement",g.webchat.placement);const s=await fetch(t,{method:"GET",headers:bt.headers}),i=await s.json();return bt.business.data||(bt.business.setData(i.business),bt.business.setLocale(i.locale)),(new DOMParser).parseFromString(i.html,"text/html").querySelector("article")}static appendWebchatOverrides(e){const{appearance:t,whatsapp:s}=g.webchat;this.appendIfSupplied(e,"webchat[appearance][header][name]",t.header?.name),this.appendIfSupplied(e,"webchat[appearance][launcher][icon_url]",t.launcher?.iconUrl),this.appendIfSupplied(e,"webchat[handoff][identifier]",s.number),this.appendIfSupplied(e,"webchat[handoff][restrict_to_channel]",s.restrictToChannel)}static appendIfSupplied(e,t,s){null!=s&&e.searchParams.append(t,String(s))}},C=class{static get endpoint(){return g.endpoint("public/widgets/whatsapp")}static async get(e){const t=new URL(`${this.endpoint}/${e}`);t.searchParams.append("locale",o.toString()),t.searchParams.append("placement",g.whatsapp.placement),this.appendWhatsAppOverrides(t);const s=await this.fetchWidget(t);if(!s.ok)return null;const i=await this.parseWidgetResponse(s);return i?(bt.business.data||(bt.business.setData(i.business),bt.business.setLocale(i.locale)),(new DOMParser).parseFromString(i.html,"text/html").querySelector("article")):null}static appendWhatsAppOverrides(e){const{appearance:t,body:s,number:i}=g.whatsapp;this.appendIfSupplied(e,"whatsapp[appearance][launcher][icon_url]",t.launcher?.iconUrl),this.appendIfSupplied(e,"whatsapp[number]",i),this.appendIfSupplied(e,"whatsapp[body]",s)}static appendIfSupplied(e,t,s){null!=s&&e.searchParams.append(t,String(s))}static async fetchWidget(e){try{return await fetch(e,{method:"GET",headers:bt.headers})}catch(e){return{ok:!1}}}static async parseWidgetResponse(e){try{return await e.json()}catch(e){return null}}},O=class{static get endpoint(){return g.endpoint("public/acks")}static async send(e={}){const t={...e,session:bt.session,at:(new Date).toISOString()};fetch(this.endpoint,{method:"POST",headers:bt.headers,body:JSON.stringify(t),keepalive:!0})}},A=class{static get endpoint(){return g.endpoint("public/push/identities")}static async create(e={}){const t=await fetch(this.endpoint,{method:"POST",keepalive:!0,headers:bt.headers,body:JSON.stringify({...e,session:bt.session,origin:window.location.origin})});return new y(t.ok,t)}static async destroy(e={}){const t=await fetch(this.endpoint,{method:"DELETE",keepalive:!0,headers:bt.headers,body:JSON.stringify({...e,session:bt.session,origin:window.location.origin})});return new y(t.ok,t)}},E=class{static get endpoint(){return g.endpoint("public/push/alerts")}static async create({section:e,kind:t,page:s}){const i=await fetch(this.endpoint,{method:"POST",keepalive:!0,headers:bt.headers,body:JSON.stringify({session:bt.session,section:e,kind:t,page:s})});return new y(i.ok,i)}};function x(e){const t=JSON.stringify(e);return"undefined"==typeof Blob?t.length<6e4:new Blob([t]).size<6e4}class M{static get businesses(){return f}static get events(){return v}static get forms(){return w}static get webchats(){return S}static get whatsappWidgets(){return C}static get identifications(){return T}static get acks(){return O}static get pushAlerts(){return E}static get pushIdentities(){return A}}const k={en:{white_label:{powered_by:"By"},errors:{parameter_not_unique:"This value is taken.",blank:"This field is required."},forms:{phone:"Click the link sent via SMS to verify your submission.",email:"Click the link sent via email to verify your submission.",phone_and_email:"Click the links sent via SMS and email to verify your submission.",none:"Your submission has been received."}},es:{white_label:{powered_by:"Por"},errors:{parameter_not_unique:"Este valor ya está en uso.",blank:"Este campo es obligatorio."},forms:{phone:"Haga clic en el enlace enviado por SMS para verificar su envío.",email:"Haga clic en el enlace enviado por e-mail para verificar su envío.",phone_and_email:"Haga clic en los enlaces enviados por SMS y e-mail para verificar su envío.",none:"Su envío ha sido recibido."}}},I="data-hellotext-stylesheet";class L{constructor(e){this.id=e,this.data=null,this.stylesheet=null,this.stylesheetLoaded=Promise.resolve(!1)}async hydrate(){try{const e=await f.get(this.id);if(!1===e.ok)return null;const t=await e.json();return t?(this.setData(t),t.locale&&this.setLocale(t.locale),t):null}catch(e){return null}}setData(e){this.data=e,"undefined"!=typeof document&&e.style_url?(this.stylesheet=this.constructor.ensureStylesheet(e.style_url),this.stylesheetLoaded=this.constructor.waitForStylesheet(this.stylesheet)):(this.stylesheet=null,this.stylesheetLoaded=Promise.resolve(!1))}static get stylesheetSelector(){return`link[rel="stylesheet"][${I}]`}static ensureStylesheet(e){const t=this.normalizedStylesheetUrl(e),s=this.stylesheetLinks.find(e=>e.href===t);if(s)return s.setAttribute(I,"true"),s;const i=document.createElement("link");return i.rel="stylesheet",i.href=e,i.setAttribute(I,"true"),this.waitForStylesheet(i),document.head.append(i),i}static get stylesheetLinks(){return"undefined"==typeof document?[]:Array.from(document.querySelectorAll(this.stylesheetSelector))}static get latestStylesheet(){return this.stylesheetLinks[this.stylesheetLinks.length-1]}static normalizedStylesheetUrl(e){try{return new URL(e,document.baseURI).href}catch(t){return e}}static waitForStylesheet(e){return e?this.stylesheetIsLoaded(e)?Promise.resolve(!0):"false"===e.dataset.hellotextStylesheetLoaded?Promise.resolve(!1):(e._hellotextStylesheetLoaded||(e._hellotextStylesheetLoaded=new Promise(t=>{let s;const i=i=>{clearTimeout(s),e.removeEventListener("load",n),e.removeEventListener("error",r),e.dataset.hellotextStylesheetLoaded=i?"true":"false",t(i)},n=()=>i(this.stylesheetIsLoaded(e)),r=()=>i(!1);e.addEventListener("load",n),e.addEventListener("error",r),s=setTimeout(()=>i(this.stylesheetIsLoaded(e)),1e4),s.unref&&s.unref()})),e._hellotextStylesheetLoaded):Promise.resolve(!1)}static stylesheetIsLoaded(e){return"true"===e.dataset.hellotextStylesheetLoaded||!!e.sheet}get subscription(){return this.data.subscription}get country(){return this.data.country}get enabledWhitelist(){return"disabled"!==this.data.whitelist}setLocale(e){if(!k[e])return console.warn(`Locale ${e} not found`);this.data||(this.data={}),this.data.locale=e}get locale(){return k[this.data.locale]}get features(){return this.data.features}}class _{static set(e,t){if("undefined"!=typeof document){const s="https:"===window.location.protocol?"; Secure":"",i=P.getRootDomain(),n=31536e4;document.cookie=i?`${e}=${t}; path=/${s}; domain=${i}; max-age=${n}; SameSite=Lax`:`${e}=${t}; path=/${s}; max-age=${n}; SameSite=Lax`}return"hello_session"===e&&bt.eventEmitter.dispatch("session-set",t),"hello_utm"===e&&bt.eventEmitter.dispatch("utm-set",t),t}static get(e){return"undefined"!=typeof document?document.cookie.match("(^|;)\\s*"+e+"\\s*=\\s*([^;]+)")?.pop():void 0}static delete(e){if("undefined"!=typeof document){const t=P.getRootDomain(),s="https:"===window.location.protocol?"; Secure":"";document.cookie=t?`${e}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT${s}; domain=${t}; SameSite=Lax`:`${e}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT${s}; SameSite=Lax`}}}class N{constructor(){const e=new URLSearchParams(window.location.search),t={source:e.get("utm_source"),medium:e.get("utm_medium"),campaign:e.get("utm_campaign"),term:e.get("utm_term"),content:e.get("utm_content")};this.save(t)}save(e){if(!e.source||!e.medium)return;const t=Object.fromEntries(Object.entries(e).filter(([e,t])=>t));t.observed_at=(new Date).toISOString(),_.set("hello_utm",JSON.stringify(t))}get current(){try{return JSON.parse(_.get("hello_utm"))||{}}catch(e){return{}}}}class P{constructor(e=null){this.utm=new N,this._url=e}get url(){return null!==this._url&&void 0!==this._url?this._url:window.location.href}get title(){return document.title}get path(){if(this._url)try{return new URL(this._url).pathname}catch(e){return"/"}return window.location.pathname}get utmParams(){return this.utm.current}get trackingData(){return{page:{url:this.url,title:this.title,path:this.path},utm_params:this.utmParams}}get domain(){try{const e=this.url;if(!e)return null;const t=new URL(e).hostname;return P.getRootDomain(t)}catch(e){return null}}static getRootDomain(e=null){try{if(!e){if("undefined"==typeof window||!window.location?.hostname)return null;e=window.location.hostname}const t=e.split(".");if(t.length<=1)return e;const s=["myshopify.com","vtexcommercestable.com.br","myvtex.com","wixsite.com"];for(const e of s){const s=e.split(".");if(t.slice(-s.length).join(".")===e&&t.length>s.length)return`.${t.slice(-(s.length+1)).join(".")}`}const i=t[t.length-1],n=t[t.length-2];return t.length>2&&2===i.length&&n.length<=3?`.${t.slice(-3).join(".")}`:`.${t.slice(-2).join(".")}`}catch(e){return null}}}class F{static#n;static#r;static#a;static get session(){return this.#n}static get ackPayload(){return{utm_params:this.#a?.utmParams||{}}}static set session(e){const t=_.get("hello_session");return this.#n=e,_.set("hello_session",e),t!==e&&_.delete("hello_session_ack_at"),_.get("hello_session_ack_at")||(M.acks.send(this.ackPayload),_.set("hello_session_ack_at",(new Date).toISOString())),this.#n}static initialize(e=new P){this.#a=e,this.#r=new b,this.session=this.#r.session||g.session||_.get("hello_session"),!this.session&&g.autoGenerateSession&&(this.session=crypto.randomUUID())}}class R{static build(e){const t=document.createElement("article"),s=document.createElement("label"),i=document.createElement("input");s.innerText=e.label,i.type=e.type,i.required=e.required,i.placeholder=e.placeholder,["first_name","last_name"].includes(e.kind)?(i.type="text",i.id=i.name=e.kind,s.setAttribute("for",e.kind)):(i.type=e.type,"email"===e.type?(i.id=i.name="email",s.setAttribute("for","email")):"tel"===i.type?(i.id=i.name="phone",s.setAttribute("for","phone"),i.value=`+${bt.business.country.prefix}`,i.setAttribute("data-default-value",`+${bt.business.country.prefix}`)):(i.name=i.id=`property_by_id[${e.property}]`,s.setAttribute("for",`property_by_id[${e.property}]`)));const n=document.createElement("main");n.appendChild(s),n.appendChild(i),t.appendChild(n),t.setAttribute("data-hellotext--form-target","inputContainer"),i.setAttribute("data-hellotext--form-target","input");const r=document.createElement("div");return r.setAttribute("data-error-container",""),t.appendChild(r),t}}class D{static build(){const e=document.createElement("div");return e.innerHTML=this.#o(),e.firstElementChild}static#o(){const e=`https://www.hellotext.com?hello_session=${bt.session}`;return`\n
\n ${bt.business.locale.white_label.powered_by}\n\n \n \n Hellotext\n \n \n \n
\n `}}function j(e,t){(null==t||t>e.length)&&(t=e.length);for(var s=0,i=Array(t);s2?s-2:0),n=2;n1?t-1:0),i=1;i1?s-1:0),n=1;n2&&void 0!==arguments[2]?arguments[2]:ie;if(V&&V(e,null),!se(t))return e;let i=t.length;for(;i--;){let n=t[i];if("string"==typeof n){const e=s(n);e!==n&&(q(t)||(t[i]=e),n=e)}e[n]=!0}return e}function we(e){for(let t=0;t/g),Re=H(/\${[\w\W]*/g),De=H(/^data-[\-\w.\u00B7-\uFFFF]+$/),je=H(/^aria-[\-\w]+$/),Be=H(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),$e=H(/^(?:\w+script|data):/i),Ve=H(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),qe=H(/^html$/i),Ue=H(/^[a-z][.\w]*(-[.\w]+)+$/i),ze=H(/<[/\w!]/g),We=H(/<[/\w]/g),He=H(/<\/no(script|embed|frames)/i),Ke=H(/\/>/i),Ge=["style","script","xmp","iframe","noembed","noframes","plaintext","noscript"],Je=W(ve({},Ge)),Ye=function(){const e={};return Z(Ge,t=>{e[t]=H(new RegExp("])","i"))}),W(e)}(),Ze=function(){return"undefined"==typeof window?null:window},Xe=function(e,t,s,i){return pe(e,t)&&se(e[t])?ve(i.base?Te(i.base):{},e[t],i.transform):s},Qe=function(e,t,s){const i=pe(e,t)?e[t]:void 0;return i&&"object"==typeof i?Te(i):s()};var et=function e(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:Ze();const s=t=>e(t);if(s.version="3.4.14",s.removed=[],!t||!t.document||9!==t.document.nodeType||!t.Element)return s.isSupported=!1,s;let i=t.document;const n=i,r=n.currentScript;t.DocumentFragment;const a=t.HTMLTemplateElement,o=t.Node,c=t.Element,l=t.NodeFilter;void 0===t.NamedNodeMap&&(t.NamedNodeMap||t.MozNamedAttrMap),t.HTMLFormElement;const h=t.DOMParser,u=t.trustedTypes,d=c.prototype,p=Se(d,"cloneNode"),m=Se(d,"remove"),g=Se(d,"nextSibling"),f=Se(d,"childNodes"),b=Se(d,"parentNode"),y=Se(d,"shadowRoot"),v=Se(d,"attributes"),w=o&&o.prototype?Se(o.prototype,"nodeType"):null,T=o&&o.prototype?Se(o.prototype,"nodeName"):null,S=o&&o.prototype?Se(o.prototype,"ownerDocument"):null,C=function(e){return w?w(e):e.nodeType},O=function(e){return T?T(e):e.nodeName};if("function"==typeof a){const e=i.createElement("template");e.content&&e.content.ownerDocument&&(i=e.content.ownerDocument)}let A,E,x="",M=!1,k=0;const I=function(){if(k>0)throw fe('A configured TRUSTED_TYPES_POLICY callback (createHTML or createScriptURL) must not call DOMPurify.sanitize, as that causes infinite recursion. Do not pass a policy whose callbacks wrap DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted Types" section of the README.')},L=function(e){I(),k++;try{return A.createHTML(e)}finally{k--}},_=i,N=_.implementation,P=_.createNodeIterator,F=_.createDocumentFragment,R=_.getElementsByTagName,D=n.importNode;let j={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]};s.isSupported="function"==typeof $&&"function"==typeof b&&N&&void 0!==N.createHTMLDocument;const B=Pe,V=Fe,q=Re,U=De,z=je,G=$e,J=Ve,Y=Ue;let be=Be,ye=null;const we=ve({},[...Ce,...Oe,...Ae,...xe,...ke]);let Ge=null;const et=ve({},[...Ie,...Le,..._e,...Ne]);let tt=Object.seal(K(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),st=null,it=null;const nt=Object.seal(K(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}}));let rt=!0,at=!0,ot=!1,ct=!0,lt=!1,ht=!0,ut=!1,dt=!1,pt=null,mt=null,gt=!1,ft=!1,bt=!1,yt=!1,vt=!0,wt=!1;const Tt="user-content-";let St=!0,Ct=!1,Ot={},At=null;const Et=ve({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","selectedcontent","style","svg","template","thead","title","video","xmp"]);let xt=null;const Mt=ve({},["audio","video","img","source","image","track"]);let kt=null;const It=ve({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Lt="http://www.w3.org/1998/Math/MathML",_t="http://www.w3.org/2000/svg",Nt="http://www.w3.org/1999/xhtml";let Pt=Nt,Ft=!1,Rt=null;const Dt=ve({},[Lt,_t,Nt],ne),jt=W(["mi","mo","mn","ms","mtext"]);let Bt=ve({},jt);const $t=W(["annotation-xml"]);let Vt=ve({},$t);const qt=ve({},["title","style","font","a","script"]);let Ut=null;const zt=["application/xhtml+xml","text/html"];let Wt=null,Ht=null;const Kt=i.createElement("form"),Gt=function(e){return e instanceof RegExp||e instanceof Function},Jt=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(Ht&&Ht===e)return;e&&"object"==typeof e||(e={}),e=Te(e),Ut=-1===zt.indexOf(e.PARSER_MEDIA_TYPE)?"text/html":e.PARSER_MEDIA_TYPE,Wt="application/xhtml+xml"===Ut?ne:ie,ye=Xe(e,"ALLOWED_TAGS",we,{transform:Wt}),Ge=Xe(e,"ALLOWED_ATTR",et,{transform:Wt}),Rt=Xe(e,"ALLOWED_NAMESPACES",Dt,{transform:ne}),kt=Xe(e,"ADD_URI_SAFE_ATTR",It,{transform:Wt,base:It}),xt=Xe(e,"ADD_DATA_URI_TAGS",Mt,{transform:Wt,base:Mt}),At=Xe(e,"FORBID_CONTENTS",Et,{transform:Wt}),st=Xe(e,"FORBID_TAGS",Te({}),{transform:Wt}),it=Xe(e,"FORBID_ATTR",Te({}),{transform:Wt}),Ot=!!pe(e,"USE_PROFILES")&&(e.USE_PROFILES&&"object"==typeof e.USE_PROFILES?Te(e.USE_PROFILES):e.USE_PROFILES),rt=!1!==e.ALLOW_ARIA_ATTR,at=!1!==e.ALLOW_DATA_ATTR,ot=e.ALLOW_UNKNOWN_PROTOCOLS||!1,ct=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,lt=e.SAFE_FOR_TEMPLATES||!1,ht=!1!==e.SAFE_FOR_XML,ut=e.WHOLE_DOCUMENT||!1,ft=e.RETURN_DOM||!1,bt=e.RETURN_DOM_FRAGMENT||!1,yt=e.RETURN_TRUSTED_TYPE||!1,gt=e.FORCE_BODY||!1,vt=!1!==e.SANITIZE_DOM,wt=e.SANITIZE_NAMED_PROPS||!1,St=!1!==e.KEEP_CONTENT,Ct=e.IN_PLACE||!1,be=function(e){try{return ge(e,""),!0}catch(e){return!1}}(e.ALLOWED_URI_REGEXP)?e.ALLOWED_URI_REGEXP:Be,Pt="string"==typeof e.NAMESPACE?e.NAMESPACE:Nt,Bt=Qe(e,"MATHML_TEXT_INTEGRATION_POINTS",()=>ve({},jt)),Vt=Qe(e,"HTML_INTEGRATION_POINTS",()=>ve({},$t));const t=Qe(e,"CUSTOM_ELEMENT_HANDLING",()=>K(null));if(tt=K(null),pe(t,"tagNameCheck")&&Gt(t.tagNameCheck)&&(tt.tagNameCheck=t.tagNameCheck),pe(t,"attributeNameCheck")&&Gt(t.attributeNameCheck)&&(tt.attributeNameCheck=t.attributeNameCheck),pe(t,"allowCustomizedBuiltInElements")&&"boolean"==typeof t.allowCustomizedBuiltInElements&&(tt.allowCustomizedBuiltInElements=t.allowCustomizedBuiltInElements),H(tt),lt&&(at=!1),bt&&(ft=!0),Ot&&(ye=ve({},ke),Ge=K(null),!0===Ot.html&&(ve(ye,Ce),ve(Ge,Ie)),!0===Ot.svg&&(ve(ye,Oe),ve(Ge,Le),ve(Ge,Ne)),!0===Ot.svgFilters&&(ve(ye,Ae),ve(Ge,Le),ve(Ge,Ne)),!0===Ot.mathMl&&(ve(ye,xe),ve(Ge,_e),ve(Ge,Ne))),nt.tagCheck=null,nt.attributeCheck=null,pe(e,"ADD_TAGS")&&("function"==typeof e.ADD_TAGS?nt.tagCheck=e.ADD_TAGS:se(e.ADD_TAGS)&&(ye===we&&(ye=Te(ye)),ve(ye,e.ADD_TAGS,Wt))),pe(e,"ADD_ATTR")&&("function"==typeof e.ADD_ATTR?nt.attributeCheck=e.ADD_ATTR:se(e.ADD_ATTR)&&(Ge===et&&(Ge=Te(Ge)),ve(Ge,e.ADD_ATTR,Wt))),pe(e,"ADD_FORBID_CONTENTS")&&se(e.ADD_FORBID_CONTENTS)&&(At===Et&&(At=Te(At)),ve(At,e.ADD_FORBID_CONTENTS,Wt)),St&&(ye["#text"]=!0),ut&&ve(ye,["html","head","body"]),ye.table&&(ve(ye,["tbody"]),delete st.tbody),e.TRUSTED_TYPES_POLICY){if("function"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw fe('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw fe('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');const t=A;A=e.TRUSTED_TYPES_POLICY;try{x=L("")}catch(e){throw A=t,e}}else null===e.TRUSTED_TYPES_POLICY?(A=void 0,x=""):(void 0===A&&(M||(E=function(e,t){if("object"!=typeof e||"function"!=typeof e.createPolicy)return null;let s=null;const i="data-tt-policy-suffix";t&&t.hasAttribute(i)&&(s=t.getAttribute(i));const n="dompurify"+(s?"#"+s:"");try{return e.createPolicy(n,{createHTML:e=>e,createScriptURL:e=>e})}catch(e){return console.warn("TrustedTypes policy "+n+" could not be created."),null}}(u,r),M=!0),A=E),A&&"string"==typeof x&&(x=L("")));W&&W(e),Ht=e},Yt=ve({},[...Oe,...Ae,...Ee]),Zt=ve({},[...xe,...Me]),Xt=function(e){ee(s.removed,{element:e});try{b(e).removeChild(e)}catch(t){if(m(e),!b(e))throw fe("a node selected for removal could not be detached from its tree and cannot be safely returned; refusing to sanitize in place")}},Qt=function(e,t,s){try{e.removeAttributeNode(t)}catch(t){try{e.removeAttribute(s)}catch(e){}}},es=function(e){is(e);const t=f(e);if(t){const e=[];Z(t,t=>{ee(e,t)}),Z(e,e=>{try{m(e)}catch(e){}})}const s=v(e);if(s)for(let t=s.length-1;t>=0;--t){const i=s[t],n=i&&i.name;"string"==typeof n&&Qt(e,i,n)}},ts=function(e,t,i){if(!i)try{i=t.getAttributeNode(e)}catch(e){i=null}ee(s.removed,{attribute:i||null,from:t});try{i?t.removeAttributeNode(i):t.removeAttribute(e)}catch(s){try{t.removeAttribute(e)}catch(e){}}if("is"===e)if(ft||bt)try{Xt(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},ss=function(e){const t=v(e);if(t)for(let s=t.length-1;s>=0;--s){const i=t[s],n=i&&i.name;"string"!=typeof n||Ge[Wt(n)]||Qt(e,i,n)}},is=function(e){const t=[e];for(;t.length>0;){const e=t.pop();1===C(e)&&ss(e);const s=f(e);if(s)for(let e=s.length-1;e>=0;--e)t.push(s[e])}},ns=function(e,t){return!!ht&&("patchsrc"===e||"for"===e&&"label"!==t&&"output"!==t)},rs=function(e){let t=null,s=null;if(gt)e=""+e;else{const t=re(e,/^[\r\n\t ]+/);s=t&&t[0]}"application/xhtml+xml"===Ut&&Pt===Nt&&(e=''+e+"");const n=A?L(e):e;if(Pt===Nt)try{t=(new h).parseFromString(n,Ut)}catch(e){}if(!t||!t.documentElement){t=N.createDocument(Pt,"template",null);try{t.documentElement.innerHTML=Ft?x:n}catch(e){}}const r=t.body||t.documentElement;return e&&s&&r.insertBefore(i.createTextNode(s),r.childNodes[0]||null),Pt===Nt?R.call(t,ut?"html":"body")[0]:ut?t.documentElement:r},as=function(e){const t=S?S(e):e.ownerDocument;return P.call(t||e,e,l.SHOW_ELEMENT|l.SHOW_COMMENT|l.SHOW_TEXT|l.SHOW_PROCESSING_INSTRUCTION|l.SHOW_CDATA_SECTION,null)},os=function(e){return e=ae(e,B," "),e=ae(e,V," "),ae(e,q," ")},cs=function(e){var t;e.normalize();const s=S?S(e):e.ownerDocument,i=P.call(s||e,e,l.SHOW_TEXT|l.SHOW_COMMENT|l.SHOW_CDATA_SECTION|l.SHOW_PROCESSING_INSTRUCTION,null);let n=i.nextNode();for(;n;)n.data=os(n.data),n=i.nextNode();const r=null===(t=e.querySelectorAll)||void 0===t?void 0:t.call(e,"template");r&&Z(r,e=>{hs(e.content)&&cs(e.content)})},ls=function(e){const t=T?T(e):null;return"string"==typeof t&&"form"===Wt(t)&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||e.attributes!==v(e)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes||e.nodeType!==w(e)||e.childNodes!==f(e))},hs=function(e){if(!w||"object"!=typeof e||null===e)return!1;try{return 11===w(e)}catch(e){return!1}},us=function(e){if(!w||"object"!=typeof e||null===e)return!1;try{return"number"==typeof w(e)}catch(e){return!1}};function ds(e,t,i){0!==e.length&&Z(e,e=>{e.call(s,t,i,Ht)})}const ps=function(e,t){if(e instanceof RegExp)return ge(e,t);if(e instanceof Function){for(var s=arguments.length,i=new Array(s>2?s-2:0),n=2;n=0;--n){const r=e===s?p(i[n],!0):i[n];t.insertBefore(r,g(e))}}return Xt(e),!0}(e,i,t);return!1===s&&ds(j.afterSanitizeElements,e,null),s}if(1===C(e)&&!function(e){let t=b(e);t&&t.tagName||(t={namespaceURI:Pt,tagName:"template"});const s=ie(e.tagName),i=ie(t.tagName);return!!Rt[e.namespaceURI]&&(e.namespaceURI===_t?function(e,t,s){return t.namespaceURI===Nt?"svg"===e:t.namespaceURI===Lt?"svg"===e&&("annotation-xml"===s||Bt[s]):Boolean(Yt[e])}(s,t,i):e.namespaceURI===Lt?function(e,t,s){return t.namespaceURI===Nt?"math"===e:t.namespaceURI===_t?"math"===e&&Vt[s]:Boolean(Zt[e])}(s,t,i):e.namespaceURI===Nt?function(e,t,s){return!(t.namespaceURI===_t&&!Vt[s])&&!(t.namespaceURI===Lt&&!Bt[s])&&!Zt[e]&&(qt[e]||!Yt[e])}(s,t,i):!("application/xhtml+xml"!==Ut||!Rt[e.namespaceURI]))}(e))return Xt(e),!0;if(("noscript"===i||"noembed"===i||"noframes"===i)&&ge(He,e.innerHTML))return Xt(e),!0;if(lt&&3===e.nodeType){const t=os(e.textContent);e.textContent!==t&&(ee(s.removed,{element:e.cloneNode()}),e.textContent=t)}return ds(j.afterSanitizeElements,e,null),!1},bs=function(e,t,s){if(it[t])return!1;if(ns(t,e))return!1;if(vt&&("id"===t||"name"===t)&&(s in i||s in Kt))return!1;const n=Ge[t]||nt.attributeCheck instanceof Function&&nt.attributeCheck(t,e);return!(!at||!ge(U,t))||!(!rt||!ge(z,t))||(n?!(!kt[t]&&!ge(be,ae(s,J,""))&&("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==oe(s,"data:")||!xt[e])&&(!ot||ge(G,ae(s,J,"")))&&s):vs(e)&&ps(tt.tagNameCheck,e)&&ps(tt.attributeNameCheck,t,e)||"is"===t&&tt.allowCustomizedBuiltInElements&&ps(tt.tagNameCheck,s))},ys=ve({},["annotation-xml","color-profile","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","missing-glyph"]),vs=function(e){return!ys[ie(e)]&&ge(Y,e)},ws=function(e,t,s,i){if(A&&"object"==typeof u&&"function"==typeof u.getAttributeType&&!s)switch(u.getAttributeType(e,t)){case"TrustedHTML":return L(i);case"TrustedScriptURL":return function(e){I(),k++;try{return A.createScriptURL(e)}finally{k--}}(i)}return i},Ts=function(e,t,i,n){try{i?e.setAttributeNS(i,t,n):e.setAttribute(t,n),ls(e)?Xt(e):Q(s.removed)}catch(s){ts(t,e)}},Ss=function(e){ds(j.beforeSanitizeAttributes,e,null);const t=e.attributes;if(!t||ls(e))return;Ge=ms(j.uponSanitizeAttribute,Ge,et,mt);const s={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:Ge,forceKeepAttr:void 0};let i=t.length;const n=Wt(e.nodeName);for(;i--;){const r=t[i],a=r.name,o=r.namespaceURI,c=r.value,l=Wt(a),h=c;let u="value"===a?h:ce(h);s.attrName=l,s.attrValue=u,s.keepAttr=!0,s.forceKeepAttr=void 0,ds(j.uponSanitizeAttribute,e,s),u=s.attrValue,!wt||"id"!==l&&"name"!==l||0===oe(u,Tt)||(ts(a,e,r),u=Tt+u),ht&&ge(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i,u)||"attributename"===l&&re(u,"href")?ts(a,e,r):s.forceKeepAttr||(!s.keepAttr||!ct&&ge(Ke,u)?ts(a,e,r):(lt&&(u=os(u)),bs(n,l,u)?(u=ws(n,l,o,u),u!==h&&Ts(e,a,o,u)):ts(a,e,r)))}ds(j.afterSanitizeAttributes,e,null)},Cs=function(e){let t=null;const s=as(e);for(ds(j.beforeSanitizeShadowDOM,e,null);t=s.nextNode();)if(ds(j.uponSanitizeShadowNode,t,null),fs(t,e),Ss(t),hs(t.content)&&Cs(t.content),1===C(t)){const e=y(t);hs(e)&&(Os(e),Cs(e))}ds(j.afterSanitizeShadowDOM,e,null)},Os=function(e){const t=[{node:e,shadow:null}];for(;t.length>0;){const e=t.pop();if(e.shadow){Cs(e.shadow);continue}const s=e.node,i=1===C(s),n=f(s);if(n)for(let e=n.length-1;e>=0;--e)t.push({node:n[e],shadow:null});if(i){const e=T?T(s):null;if("string"==typeof e&&"template"===Wt(e)){const e=s.content;hs(e)&&t.push({node:e,shadow:null})}}if(i){const e=y(s);hs(e)&&t.push({node:null,shadow:e},{node:e,shadow:null})}}};return s.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=null,r=null,a=null,o=null;if(Ft=!e,Ft&&(e="\x3c!--\x3e"),"string"!=typeof e&&!us(e)&&"string"!=typeof(e=function(e){switch(typeof e){case"string":return e;case"number":return le(e);case"boolean":return he(e);case"bigint":return ue?ue(e):"0";case"symbol":return de?de(e):"Symbol()";case"undefined":default:return me(e);case"function":case"object":{if(null===e)return me(e);const t=e,s=Se(t,"toString");if("function"==typeof s){const e=s(t);return"string"==typeof e?e:me(e)}return me(e)}}}(e)))throw fe("dirty is not a string, aborting");if(!s.isSupported)return e;dt?(ye=pt,Ge=mt):Jt(t),(j.uponSanitizeElement.length>0||j.uponSanitizeAttribute.length>0)&&(ye=Te(ye)),j.uponSanitizeAttribute.length>0&&(Ge=Te(Ge)),s.removed=[];const c=Ct&&"string"!=typeof e&&us(e);if(c){!function(e){if(!ht)return;const t=[e];for(;t.length>0;){const e=t.pop(),s=C(e);if(7===s||8===s&&ge(We,e.data)){try{m(e)}catch(e){}continue}if(1===s){const t=e,s=Wt(O(e));try{t.hasAttribute&&t.hasAttribute("patchsrc")&&t.removeAttribute("patchsrc"),t.hasAttribute&&t.hasAttribute("for")&&ns("for",s)&&t.removeAttribute("for")}catch(e){}}const i=f(e);if(i)for(let e=i.length-1;e>=0;--e)t.push(i[e])}}(e);const t=O(e);if("string"==typeof t){const s=Wt(t);if(!ye[s]||st[s])throw es(e),fe("root node is forbidden and cannot be sanitized in-place")}if(ls(e))throw es(e),fe("root node is clobbered and cannot be sanitized in-place");try{Os(e)}catch(t){throw es(e),t}}else if(us(e))i=rs("\x3c!----\x3e"),r=i.ownerDocument.importNode(e,!0),1===r.nodeType&&"BODY"===r.nodeName||"HTML"===r.nodeName?i=r:i.appendChild(r),Os(r);else{if(!ft&&!lt&&!ut&&-1===e.indexOf("<"))return A&&yt?L(e):e;if(i=rs(e),!i)return ft?null:yt?x:""}i&>&&Xt(i.firstChild);const l=c?e:i;try{const e=as(l);for(;a=e.nextNode();)fs(a,l),Ss(a),hs(a.content)&&Cs(a.content)}catch(t){throw c&&(es(e),Z(s.removed,e=>{e.element&&is(e.element)})),t}if(c)return Z(s.removed,e=>{e.element&&is(e.element)}),lt&&cs(e),e;if(ft){if(lt&&cs(i),bt)for(o=F.call(i.ownerDocument);i.firstChild;)o.appendChild(i.firstChild);else o=i;return(Ge.shadowroot||Ge.shadowrootmode)&&(o=D.call(n,o,!0)),o}let h=ut?i.outerHTML:i.innerHTML;return ut&&ye["!doctype"]&&i.ownerDocument&&i.ownerDocument.doctype&&i.ownerDocument.doctype.name&&ge(qe,i.ownerDocument.doctype.name)&&(h="\n"+h),lt&&(h=os(h)),A&&yt?L(h):h},s.setConfig=function(){Jt(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),dt=!0,pt=ye,mt=Ge},s.clearConfig=function(){Ht=null,dt=!1,pt=null,mt=null,A=E,x=""},s.isValidAttribute=function(e,t,s){Ht||Jt({});const i=Wt(e),n=Wt(t);return bs(i,n,s)},s.addHook=function(e,t){"function"==typeof t&&pe(j,e)&&ee(j[e],t)},s.removeHook=function(e,t){if(pe(j,e)){if(void 0!==t){const s=X(j[e],t);return-1===s?void 0:te(j[e],s,1)[0]}return Q(j[e])}},s.removeHooks=function(e){pe(j,e)&&(j[e]=[])},s.removeAllHooks=function(){j={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},s}();const tt={ADD_ATTR:["target"],ALLOW_DATA_ATTR:!1,RETURN_DOM_FRAGMENT:!0,USE_PROFILES:{html:!0}},st={ADD_ATTR:["target"],ADD_TAGS:["hellotext-icon"],RETURN_DOM_FRAGMENT:!0,USE_PROFILES:{html:!0,svg:!0}};function it(e,t){const s=et.sanitize(e,t);return s.querySelectorAll('a[target="_blank"]').forEach(e=>{const t=new Set(e.rel.split(/\s+/).filter(Boolean));t.add("noopener"),t.add("noreferrer"),e.rel=Array.from(t).join(" ")}),s}function nt(e,t){e.replaceChildren(function(e){return it(e,tt)}(t))}class rt{constructor(e,t=null){this.data=e,this.element=t||document.querySelector(`[data-hello-form="${this.id}"]`)||document.createElement("form")}async mount({ifCompleted:e=!0}={}){if(e&&this.hasBeenCompleted)return this.element?.remove(),bt.eventEmitter.dispatch("form:completed",{id:this.id,...JSON.parse(localStorage.getItem(`hello-form-${this.id}`))});const t=this.data.steps[0];this.buildHeader(t.header),this.buildInputs(t.inputs),this.buildButton(t.button),this.buildFooter(t.footer),this.elementAttributes.forEach(e=>{this.element.setAttribute(e.name,e.value)}),document.contains(this.element)||document.body.appendChild(this.element),bt.business.features.white_label||this.element.prepend(D.build())}buildHeader(e){const t=this.#c("[data-form-header]","header");nt(t,e.content),this.element.querySelector("[data-form-header]")?this.element.querySelector("[data-form-header]").replaceWith(t):this.element.prepend(t)}buildInputs(e){const t=this.#c("[data-form-inputs]","main");e.map(e=>R.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)}buildButton(e){const t=this.#c("[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)}buildFooter(e){const t=this.#c("[data-form-footer]","footer");nt(t,e.content),this.element.querySelector("[data-form-footer]")?this.element.querySelector("[data-form-footer]").replaceWith(t):this.element.appendChild(t)}markAsCompleted(e){const t={state:"completed",id:this.id,data:e,completedAt:(new Date).getTime()};localStorage.setItem(`hello-form-${this.id}`,JSON.stringify(t)),bt.eventEmitter.dispatch("form:completed",t)}get hasBeenCompleted(){return null!==localStorage.getItem(`hello-form-${this.id}`)}get id(){return this.data.id}get localeAuthKey(){const 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"}get elementAttributes(){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)}]}#c(e,t){const s=this.element.querySelector(e);if(s)return s.cloneNode(!0);const i=document.createElement(t);return i.setAttribute(e.replace("[","").replace("]",""),""),i}}class at extends Error{constructor(){super("You need to initialize before tracking events. Call Hellotext.initialize and pass your public business id"),this.name="NotInitializedError"}}class ot{constructor(){this.forms=[],this.includes=this.includes.bind(this),this.excludes=this.excludes.bind(this),this.add=this.add.bind(this),"undefined"!=typeof MutationObserver&&(this.mutationObserver=new MutationObserver(this.formMutationObserver.bind(this)),this.mutationObserver.observe(document.body,{childList:!0,subtree:!0}))}collectExistingFormsOnPage(){Array.from(document.querySelectorAll("[data-hello-form]")).length>0&&this.collect()}formMutationObserver(e){e.find(e=>"childList"===e.type&&e.addedNodes.length>0)&&Array.from(document.querySelectorAll("[data-hello-form]")).length>0&&this.collect()}async collect(){if(bt.notInitialized)throw new at;if(this.fetching)return;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.");const e=this.#l;if(0===e.length)return;const t=e.map(e=>w.get(e).then(e=>e.json()));this.fetching=!0,await Promise.all(t).then(e=>e.forEach(this.add)).then(()=>bt.eventEmitter.dispatch("forms:collected",this)).then(()=>this.fetching=!1),g.forms.autoMount&&this.forms.forEach(e=>e.mount())}forEach(e){this.forms.forEach(e)}map(e){return this.forms.map(e)}add(e){this.includes(e.id)||(bt.business.data||(bt.business.setData(e.business),bt.business.setLocale(o.toString())),bt.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 rt(e)))}getById(e){return this.forms.find(t=>t.id===e)}getByIndex(e){return this.forms[e]}includes(e){return this.forms.some(t=>t.id===e)}excludes(e){return!this.includes(e)}get length(){return this.forms.length}get#l(){return Array.from(document.querySelectorAll("[data-hello-form]")).map(e=>e.dataset.helloForm).filter(this.excludes)}}class ct{constructor(e){this.publicKey=e.public_key,this.serviceWorkerUrl=g.push.serviceWorkerUrl,this.channelId=g.push.channelId,this.ready=null,this.registrationPromise=null,this.subscribePromise=null,this.unsubscribePromise=null,this.syncPromise=null,this.subscription=null,this.retryTimeout=null,this.retryAttempts=0,this.disposed=!1}initialize(){return this.ready=this.restoreSubscription(),this.ready}async restoreSubscription(){const e=await this.getRegistration(),t=await e.pushManager.getSubscription();!this.disposed&&!this.unsubscribePromise&&t&&this.owns(t)&&(this.subscription=t,await this.sync())}subscribe(){if(this.disposed)return Promise.resolve();if(this.unsubscribePromise)return Promise.reject(new Error("Push unsubscribe is in progress"));if(this.subscribePromise)return this.subscribePromise;const e="default"===Notification.permission?Notification.requestPermission():Promise.resolve(Notification.permission);return this.subscribePromise=this.createSubscription(e).finally(()=>{this.subscribePromise=null}),this.subscribePromise}async createSubscription(e){if("granted"!==await e)throw new Error("Push permission was not granted");const t=await this.getRegistration();if(this.disposed)return;let s=await t.pushManager.getSubscription();if(!this.disposed){if(s&&!this.owns(s))throw new Error("The existing Push subscription belongs to a different application");if(s||=await t.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:this.applicationServerKey}),!this.disposed)return this.subscription=s,this.sync()}}unsubscribe(){return this.disposed?Promise.resolve():(this.unsubscribePromise||(this.clearRetryTimeout(),this.unsubscribePromise=this.removeSubscription().finally(()=>{this.unsubscribePromise=null})),this.unsubscribePromise)}async removeSubscription(){await(this.ready?.catch(()=>{})),await(this.subscribePromise?.catch(()=>{})),await(this.syncPromise?.catch(()=>{}));const e=await this.getRegistration(),t=await e.pushManager.getSubscription()||this.subscription;if(this.disposed)return;if(!t)return null;if(!this.owns(t))throw new Error("The existing Push subscription belongs to a different application");this.subscription=t;const s=await M.pushIdentities.destroy({subscription:t.toJSON()});return s.failed?s:this.disposed?void 0:(await t.unsubscribe(),this.subscription=null,this.clearRetryTimeout(),s)}sync(){return this.disposed?Promise.resolve():(this.syncPromise||(this.clearRetryTimeout(),this.syncPromise=this.registerIdentity().finally(()=>{this.syncPromise=null})),this.syncPromise)}async registerIdentity(){try{const e=await M.pushIdentities.create({subscription:this.subscription.toJSON(),...this.channelId?{channel_id:this.channelId}:{}});return e.succeeded?this.retryAttempts=0:this.scheduleRetry(),e}catch(e){throw this.scheduleRetry(),e}}scheduleRetry(){this.disposed||this.unsubscribePromise||this.retryTimeout||this.retryAttempts>=3||(this.retryTimeout=setTimeout(()=>{this.retryTimeout=null,this.sync().catch(()=>{})},1e3*2**this.retryAttempts),this.retryAttempts+=1)}clearRetryTimeout(){clearTimeout(this.retryTimeout),this.retryTimeout=null}dispose(){this.disposed=!0,this.clearRetryTimeout()}get subscribed(){return!!this.subscription}get applicationServerKey(){const e=this.publicKey.replace(/-/g,"+").replace(/_/g,"/");return Uint8Array.from(atob(e.padEnd(4*Math.ceil(e.length/4),"=")),e=>e.charCodeAt(0))}owns(e){const t=e.options?.applicationServerKey;if(!t)return!1;const s=new Uint8Array(t),i=this.applicationServerKey;return s.length===i.length&&s.every((e,t)=>e===i[t])}getRegistration(){return this.registrationPromise||(this.registrationPromise=this.loadRegistration().catch(e=>{throw this.registrationPromise=null,e})),this.registrationPromise}async loadRegistration(){if(this.serviceWorkerUrl){const e=await navigator.serviceWorker.register(this.serviceWorkerUrl);!e.active||e.installing||e.waiting||await e.update();const t=e.installing||e.waiting||e.active;return"activated"===t?.state?e:new Promise((s,i)=>{const n=setTimeout(()=>r(new Error("Push service worker did not become active")),1e4),r=r=>{clearTimeout(n),t?.removeEventListener("statechange",a),r?i(r):s(e)},a=()=>{"activated"===t?.state&&r(),"redundant"===t?.state&&r(new Error("Push service worker installation failed"))};t?.addEventListener("statechange",a),a()})}return new Promise((e,t)=>{const s=setTimeout(()=>t(new Error("Push service worker is not available")),1e4);navigator.serviceWorker.ready.then(t=>{clearTimeout(s),e(t)},e=>{clearTimeout(s),t(e)})})}static get supported(){return"undefined"!=typeof window&&!0===window.isSecureContext&&"undefined"!=typeof navigator&&"serviceWorker"in navigator&&"undefined"!=typeof PushManager&&"undefined"!=typeof Notification}}class lt{constructor(e,t,s){this.business=t,this.push=s,this.disposed=!1,this.showRequest=0,this.controller=null,this.element=(new DOMParser).parseFromString(e.html,"text/html").querySelector('[data-controller~="hellotext--alert"]'),this.connected=new Promise(e=>{this.resolveConnected=e}),this.onConnect=this.onConnect.bind(this),this.ready=this.render()}onConnect(e){this.controller=e.detail.controller,this.controller.alert=this,this.resolveConnected(!0)}async render(){return!!this.element&&(this.element.addEventListener("hellotext--alert:connected",this.onConnect),!(!await this.business.stylesheetLoaded||this.disposed)&&(document.body.appendChild(this.element),this.connected))}async show(e,t={}){const s=++this.showRequest;return!(!await this.ready||this.disposed||s!==this.showRequest)&&this.controller.show(e,t)}hide(){this.showRequest+=1,this.controller?.close()}dispose(){this.disposed=!0,this.hide(),this.resolveConnected(!1),this.element?.removeEventListener("hellotext--alert:connected",this.onConnect),this.element?.remove()}}class ht{static async load(e){const t=new ht({id:e,html:await M.webchats.get(e)});return t.rendered=t.render(),t}constructor(e){this.data=e,this.mounted=!1,this.rendered=Promise.resolve(!1)}async render(){return this.applyBehaviourOverride(),await this.stylesheetLoaded?(this.containerToAppendTo.appendChild(this.data.html),this.markCoexistingWidgets(),this.mounted=!0,!0):(console.warn("Hellotext webchat was not mounted because its stylesheet failed to load."),!1)}applyBehaviourOverride(){g.webchat.hasBehaviourOverride&&g.webchat.behaviour&&this.data.html.setAttribute("data-hellotext--webchat-behaviour-value",JSON.stringify(this.serializedBehaviour))}get serializedBehaviour(){const e=g.webchat.behaviour;return{trigger:this.serializeTrigger(e.trigger),delay_seconds:e.delaySeconds,first_visit_only:e.firstVisitOnly,once_per_session:e.oncePerSession}}serializeTrigger(e){return"onLoad"===e?"on_load":"onClick"===e?"on_click":e}get containerToAppendTo(){return document.querySelector(g.webchat.container)}get stylesheetLoaded(){return L.waitForStylesheet(L.latestStylesheet)}markCoexistingWidgets(){const e=this.data.html,t=document.querySelector(".hellotext--whatsapp-widget");e&&t&&(e.classList.add("hellotext--with-whatsapp-widget"),t.classList.add("hellotext--with-webchat"))}}class ut{static async load(e){const t=new ut({id:e,html:await M.whatsappWidgets.get(e)});return t.rendered=t.render(),t}constructor(e){this.data=e,this.mounted=!1,this.rendered=Promise.resolve(!1)}async render(){if(!this.data.html)return!1;const e=this.containerToAppendTo;return e?await this.stylesheetLoaded?(e.appendChild(this.data.html),this.markCoexistingWidgets(),this.mounted=!0,!0):(console.warn("Hellotext WhatsApp widget was not mounted because its stylesheet failed to load."),!1):(console.warn(`Hellotext WhatsApp widget was not mounted because the container ${g.whatsapp.container} was not found.`),!1)}get containerToAppendTo(){try{return document.querySelector(g.whatsapp.container)}catch(e){return null}}get stylesheetLoaded(){return L.waitForStylesheet(L.latestStylesheet)}markCoexistingWidgets(){const e=document.querySelector(".hellotext--webchat:not(.hellotext--whatsapp-widget)"),t=this.data.html;e&&t&&(e.classList.add("hellotext--with-whatsapp-widget"),t.classList.add("hellotext--with-webchat"))}}class dt{static get id(){return _.get("hello_user_id")}static get source(){return _.get("hello_user_source")}static get fingerprint(){return _.get("hello_user_identification_hash")}static remember(e,t,s){t&&_.set("hello_user_source",t),s&&_.set("hello_user_identification_hash",s),_.set("hello_user_id",e)}static forget(){_.delete("hello_user_id"),_.delete("hello_user_source"),_.delete("hello_user_identification_hash")}static get identificationData(){return this.id?{id:this.id,source:this.source}:{}}}function pt(e){if(null!=e){if("string"==typeof e){const t=e.trim();return""===t?void 0:t}if(Array.isArray(e))return e.map(e=>pt(e)).filter(e=>void 0!==e);if(e instanceof Date)return e.toISOString();if("object"==typeof e){const t=Object.keys(e).sort((e,t)=>e.localeCompare(t)).reduce((t,s)=>{const i=pt(e[s]);return void 0!==i&&(t[s]=i),t},{});return Object.keys(t).length>0?t:void 0}return"number"==typeof e||"boolean"==typeof e?e:void 0}}function mt(e,t,s={}){const i=pt({session:e,user_id:t,...s})||{};return JSON.stringify(i)}class gt{static matches(e,t){return!!e&&e===t}static async generate(e,t,s={}){return await async function(e){if(!globalThis.crypto?.subtle||"undefined"==typeof TextEncoder)return function(e){let t=5381;for(let s=0;s>>0).toString(16)}`}(e);const t=await globalThis.crypto.subtle.digest("SHA-256",(new TextEncoder).encode(e));return`v1:${Array.from(new Uint8Array(t)).map(e=>e.toString(16).padStart(2,"0")).join("")}`}(mt(e,t,s))}}class ft{static eventEmitter=new r;static forms;static business;static webchat;static whatsapp;static push;static alert;static async initialize(e,t={}){this.alert?.dispose(),this.alert=null,this.push?.dispose(),this.push=null;const s=new L(e);this.business=s,this.page=new P,g.assign({push:{},...t}),F.initialize(this.page),this.forms=new ot,this.query=new b;const i=await s.hydrate();if(this.business!==s)return;!1!==t.push&&i?.push?.public_key&&ct.supported&&(this.push=new ct(i.push),this.push.initialize().catch(e=>{console.warn("Hellotext Push initialization failed:",e)}),i.alert?.html&&(this.alert=new lt(i.alert,s,this.push)));const n=!1!==t.webchat&&this.mergeWebchatConfig(i&&i.webchat||{},t.webchat||{}),r=!1!==t.whatsappWidget&&this.mergeWhatsAppConfig(i&&i.whatsapp||{},t.whatsappWidget||{}),a=t.webchat&&!1!==t.webchat&&Object.prototype.hasOwnProperty.call(t.webchat,"behaviour");g.webchat.behaviourOverride=a,n&&n.id&&(g.webchat.assign(n),this.webchat=await ht.load(n.id)),r&&r.id&&(g.whatsapp.assign(r),this.whatsapp=await ut.load(r.id)),"undefined"!=typeof MutationObserver&&this.forms.collectExistingFormsOnPage()}static mergeWebchatConfig(e,t){return this.deepMergePlainObjects(e,t)}static mergeWhatsAppConfig(e,t){return this.deepMergePlainObjects(e,t)}static deepMergePlainObjects(e,t){const s={...e};return Object.entries(t).forEach(([e,t])=>{this.isPlainObject(t)&&this.isPlainObject(s[e])?s[e]=this.deepMergePlainObjects(s[e],t):s[e]=t}),s}static isPlainObject(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}static async track(e,t={}){if(this.notInitialized)throw new at;const s={...t&&t.headers||{},...this.headers},i={...dt.identificationData,...t.user_parameters||{}},n=t&&t.url?new P(t.url):this.page,r={session:this.session,user_parameters:i,action:e,...t,...n.trackingData};return delete r.headers,await M.events.create({headers:s,body:r,keepalive:x(r)})}static async identify(e,t={}){const s=await gt.generate(this.session,e,t);if(gt.matches(dt.fingerprint,s))return new y(!0,{json:async()=>({already_identified:!0})});const i=await M.identifications.create({user_id:e,...t});return i.succeeded&&dt.remember(e,t.source,s),i}static forget(){dt.forget()}static on(e,t){this.eventEmitter.addSubscriber(e,t)}static removeEventListener(e,t){this.eventEmitter.removeSubscriber(e,t)}static get session(){return F.session}static get isInitialized(){return void 0!==F.session}static get notInitialized(){return!this.business||void 0===this.business.id}static get headers(){if(this.notInitialized)throw new at;return{Authorization:`Bearer ${this.business.id}`,Accept:"application/json","Content-Type":"application/json"}}}const bt=ft,yt=new Map,vt=class extends i.xI{static values={sections:Array};static targets=["title","description","primaryAction","secondaryAction"];initialize(){this.showRequest=0,this.submitting=!1,this.onStorage=this.onStorage.bind(this)}connect(){this.dispatch("connected",{detail:{controller:this}}),window.addEventListener("storage",this.onStorage)}disconnect(){this.close(),window.removeEventListener("storage",this.onStorage)}onStorage(e){e.key===this.storageKey&&this.dismissal.dismissedUntil>Date.now()&&this.close()}async show(e,{force:t=!1,title:s,description:i,primaryAction:n,secondaryAction:r}={}){const a=++this.showRequest,o=this.sectionsValue.find(t=>t.kind===e);return o?(await(this.alert.push.ready?.catch(()=>{})),!(a!==this.showRequest||this.alert.disposed||!this.element.isConnected||(this.unavailable||!t&&this.dismissal.dismissedUntil>Date.now()?(this.close(),1):(this.titleTarget.textContent=s??o.title,this.descriptionTarget.textContent=i??o.description,this.primaryActionTarget.textContent=n??o.primary_action,this.secondaryActionTarget.textContent=r??o.secondary_action,this.kind=e,this.page=bt.page.trackingData.page,this.element.hidden=!1,this.record("shown"),bt.eventEmitter.dispatch("alert:shown",{kind:e}),0)))):(this.close(),!1)}hide(){if(this.element.hidden||this.submitting)return;const e=this.dismissal.dismissals+1,t={dismissals:e,dismissedUntil:Date.now()+864e5*(1===e?7:30)};yt.set(this.storageKey,t);try{localStorage.setItem(this.storageKey,JSON.stringify(t))}catch(e){}this.close(),this.record("dismissed"),bt.eventEmitter.dispatch("alert:dismissed",{kind:this.kind})}close(){this.showRequest+=1,this.element.hidden=!0}async subscribe(){if(!this.submitting&&!this.element.hidden){if(this.unavailable)return this.close();this.submitting=!0,this.primaryActionTarget.disabled=!0,this.secondaryActionTarget.disabled=!0,this.element.setAttribute("aria-busy","true");try{this.record("accepted"),bt.eventEmitter.dispatch("alert:accepted",{kind:this.kind});const e=await this.alert.push.subscribe();return this.unavailable&&this.close(),e?.failed&&this.dispatch("error",{detail:{response:e}}),e}catch(e){this.unavailable&&this.close(),this.dispatch("error",{detail:{error:e}})}finally{this.submitting=!1,this.primaryActionTarget.disabled=!1,this.secondaryActionTarget.disabled=!1,this.element.removeAttribute("aria-busy")}}}async record(e){try{const t=await M.pushAlerts.create({section:this.kind,kind:e,page:this.page});t.failed&&console.warn("Hellotext Smart Alert submission failed:",t)}catch(e){console.warn("Hellotext Smart Alert submission failed:",e)}}get unavailable(){return this.alert.push.disposed||this.alert.push.subscribed||"undefined"==typeof Notification||"denied"===Notification.permission}get storageKey(){return`hellotext:alert:${this.alert.business.id}`}get dismissal(){try{const e=JSON.parse(localStorage.getItem(this.storageKey));Number.isSafeInteger(e?.dismissals)&&e.dismissals>0&&Number.isFinite(e.dismissedUntil)&&e.dismissedUntil>=0&&yt.set(this.storageKey,e)}catch(e){}return yt.get(this.storageKey)||{dismissals:0,dismissedUntil:0}}},wt=class extends i.xI{static values={data:Object,step:{type:Number,default:1}};static targets=["inputContainer","input","button","otpContainer"];initialize(){this.form=new rt(this.dataValue,this.element)}connect(){super.connect(),this.element.addEventListener("submit",this.submit.bind(this)),"INPUT"!==document.activeElement.tagName&&this.inputTargets[0].focus()}async submit(e){if(e.preventDefault(),this.invalid)return this.showErrorMessages();this.clearErrorMessages(),this.formData=Object.fromEntries(new FormData(this.element)),this.buttonTarget.disabled=!0;const t=await w.submit(this.form.id,this.formData);this.buttonTarget.disabled=!1;const s=await t.json();if(t.failed)return s.errors.forEach(e=>{const{type:t,parameter:s}=e,i=this.inputTargets.find(e=>e.name===s);i.setCustomValidity(bt.business.locale.errors[t]),i.reportValidity(),i.addEventListener("input",()=>{i.setCustomValidity(""),i.reportValidity()})}),this.showErrorMessages();this.buttonTarget.style.display="none",this.element.querySelectorAll("input").forEach(e=>e.disabled=!0),this.completed()}completed(){if(this.form.markAsCompleted(this.formData),!g.forms.shouldShowSuccessMessage)return this.element.remove();"string"==typeof g.forms.successMessage?this.element.innerHTML=g.forms.successMessage:this.element.innerHTML=bt.business.locale.forms[this.form.localeAuthKey]}showErrorMessages(){this.inputTargets.forEach(e=>{const t=e.closest("article").querySelector("[data-error-container]");e.validity.valid?t.innerText="":t.innerText=e.validationMessage})}clearErrorMessages(){this.inputTargets.forEach(e=>{e.setCustomValidity(""),e.closest("article").querySelector("[data-error-container]").innerText=""})}inputTargetConnected(e){e.getAttribute("data-default-value")&&(e.value=e.getAttribute("data-default-value"))}get requiredInputs(){return this.inputTargets.filter(e=>e.required)}get invalid(){return!this.element.checkValidity()}},Tt=class extends i.xI{static values={fadeDistance:{type:Number,default:64},id:String,kind:String,pageStartOffset:{type:Number,default:0},utm:Object};static targets=["carouselContainer","leftFade","rightFade","carouselCard"];connect(){this.updateFades(),this.observeContainerSize()}disconnect(){this.resizeObserver?.disconnect()}setId({detail:e}){this.idValue=e,this.element.id=e}onScroll(){this.updateFades()}quickReply({currentTarget:e}){const t=e.closest('[data-hellotext--message-target="carouselCard"]'),s=e.closest('[data-controller~="hellotext--message"]'),i=e.dataset.text||e.textContent.trim();this.dispatch("quickReply",{detail:{id:this.idValue,product:t?.dataset.id,buttonId:e.dataset.id,body:i,cardElement:t||s||e}})}addToCart({currentTarget:e}){const t=e.closest('[data-hellotext--message-target="carouselCard"]'),{id:s,reference:i,source:n}=t.dataset,r={product:s,quantity:1};this.hasUtmValue&&bt.page.utm.save(this.utmValue),bt.eventEmitter.dispatch("cart.added",{object_parameters:{items:[{...r,...i&&{reference:i},...n&&{source:n}}]},source:{kind:this.kindValue,message_id:this.idValue,button_id:e.dataset.id}})}moveToLeft(){if(!this.hasCarouselContainerTarget)return;const e=this.getPreviousPageScrollLeft(),t=this.carouselContainerTarget.scrollLeft-e;t<1||this.carouselContainerTarget.scrollBy({left:-t,behavior:"smooth"})}moveToRight(){if(!this.hasCarouselContainerTarget)return;const e=this.getNextPageScrollLeft()-this.carouselContainerTarget.scrollLeft;e<1||this.carouselContainerTarget.scrollBy({left:e,behavior:"smooth"})}getScrollAmount(){return this.getCardScrollAmount()}getCardScrollAmount(){const e=this.carouselContainerTarget.querySelector(".message__carousel_card");return e?e.offsetWidth+this.getGap():280}getPageScrollAmount(){const e=this.carouselContainerTarget.clientWidth-this.getGap();return e>0?e:this.getCardScrollAmount()}getNextPageScrollLeft(){const e=this.getCurrentScrollLeft(),t=e+this.carouselContainerTarget.clientWidth,s=this.getCardMetrics().find(e=>e.end>t+1),i=s?this.getPageAlignedScrollLeft(s.start):e+this.getPageScrollAmount(),n=e+this.getPageScrollAmount();return this.clampScrollLeft(i>e+1?i:n)}getPreviousPageScrollLeft(){const e=this.getCurrentScrollLeft();if(e<=1)return 0;const t=Math.max(e-this.getPageScrollAmount(),0);if(t<=1)return 0;const s=this.getCardMetrics(),i=s.find(s=>s.start>=t-1&&s.startt.start{const t=this.getCardScrollLeft(e);return{start:t,end:t+e.offsetWidth}})}getCardScrollLeft(e){const t=e.getBoundingClientRect(),s=this.carouselContainerTarget.getBoundingClientRect();return t.left||t.width||s.left||s.width?t.left-s.left+this.carouselContainerTarget.scrollLeft:e.offsetLeft||0}getCurrentScrollLeft(){return this.clampScrollLeft(this.carouselContainerTarget.scrollLeft)}clampScrollLeft(e){const t=Math.max(this.carouselContainerTarget.scrollWidth-this.carouselContainerTarget.clientWidth,0);return Math.min(Math.max(e,0),t)}getGap(){const e=window.getComputedStyle(this.carouselContainerTarget),t=Number.parseFloat(e.columnGap||e.gap);return Number.isFinite(t)?t:16}getFadeDistance(){return Number.isFinite(this.fadeDistanceValue)?this.fadeDistanceValue:64}getPageStartOffset(){return Number.isFinite(this.pageStartOffsetValue)?this.pageStartOffsetValue:0}observeContainerSize(){this.hasCarouselContainerTarget&&window.ResizeObserver&&(this.resizeObserver=new ResizeObserver(()=>this.updateFades()),this.resizeObserver.observe(this.carouselContainerTarget))}updateFades(){if(!this.hasCarouselContainerTarget)return;const e=Math.max(this.carouselContainerTarget.scrollWidth-this.carouselContainerTarget.clientWidth,0);if(e<=1)return this.hideFade(this.leftFadeTarget),void this.hideFade(this.rightFadeTarget);const t=Math.min(Math.max(this.carouselContainerTarget.scrollLeft,0),e),s=this.getFadeDistance();this.setFadeOpacity(this.leftFadeTarget,t/s),this.setFadeOpacity(this.rightFadeTarget,(e-t)/s)}setFadeOpacity(e,t){const s=Math.min(Math.max(t,0),1);s<=.05?this.hideFade(e):(e.classList.remove("hidden"),e.removeAttribute("disabled"),e.removeAttribute("tabindex"),e.setAttribute("aria-hidden","false"),e.style.opacity=s.toFixed(3),e.style.pointerEvents="auto")}hideFade(e){e.style.opacity="0",e.style.pointerEvents="none",e.setAttribute("aria-hidden","true"),e.setAttribute("tabindex","-1"),"disabled"in e&&(e.disabled=!0),e.classList.add("hidden")}},St=["start","end"],Ct=["top","right","bottom","left"].reduce((e,t)=>e.concat(t,t+"-"+St[0],t+"-"+St[1]),[]),Ot=Math.min,At=Math.max,Et=Math.round,xt=Math.floor,Mt=e=>({x:e,y:e}),kt={left:"right",right:"left",bottom:"top",top:"bottom"},It={start:"end",end:"start"};function Lt(e,t,s){return At(e,Ot(t,s))}function _t(e,t){return"function"==typeof e?e(t):e}function Nt(e){return e.split("-")[0]}function Pt(e){return e.split("-")[1]}function Ft(e){return"x"===e?"y":"x"}function Rt(e){return"y"===e?"height":"width"}const Dt=new Set(["top","bottom"]);function jt(e){return Dt.has(Nt(e))?"y":"x"}function Bt(e){return Ft(jt(e))}function $t(e,t,s){void 0===s&&(s=!1);const i=Pt(e),n=Bt(e),r=Rt(n);let a="x"===n?i===(s?"end":"start")?"right":"left":"start"===i?"bottom":"top";return t.reference[r]>t.floating[r]&&(a=Ht(a)),[a,Ht(a)]}function Vt(e){return e.replace(/start|end/g,e=>It[e])}const qt=["left","right"],Ut=["right","left"],zt=["top","bottom"],Wt=["bottom","top"];function Ht(e){return e.replace(/left|right|bottom|top/g,e=>kt[e])}function Kt(e){const{x:t,y:s,width:i,height:n}=e;return{width:i,height:n,top:s,left:t,right:t+i,bottom:s+n,x:t,y:s}}function Gt(e,t,s){let{reference:i,floating:n}=e;const r=jt(t),a=Bt(t),o=Rt(a),c=Nt(t),l="y"===r,h=i.x+i.width/2-n.width/2,u=i.y+i.height/2-n.height/2,d=i[o]/2-n[o]/2;let p;switch(c){case"top":p={x:h,y:i.y-n.height};break;case"bottom":p={x:h,y:i.y+i.height};break;case"right":p={x:i.x+i.width,y:u};break;case"left":p={x:i.x-n.width,y:u};break;default:p={x:i.x,y:i.y}}switch(Pt(t)){case"start":p[a]-=d*(s&&l?-1:1);break;case"end":p[a]+=d*(s&&l?-1:1)}return p}async function Jt(e,t){var s;void 0===t&&(t={});const{x:i,y:n,platform:r,rects:a,elements:o,strategy:c}=e,{boundary:l="clippingAncestors",rootBoundary:h="viewport",elementContext:u="floating",altBoundary:d=!1,padding:p=0}=_t(t,e),m=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}}(p),g=o[d?"floating"===u?"reference":"floating":u],f=Kt(await r.getClippingRect({element:null==(s=await(null==r.isElement?void 0:r.isElement(g)))||s?g:g.contextElement||await(null==r.getDocumentElement?void 0:r.getDocumentElement(o.floating)),boundary:l,rootBoundary:h,strategy:c})),b="floating"===u?{x:i,y:n,width:a.floating.width,height:a.floating.height}:a.reference,y=await(null==r.getOffsetParent?void 0:r.getOffsetParent(o.floating)),v=await(null==r.isElement?void 0:r.isElement(y))&&await(null==r.getScale?void 0:r.getScale(y))||{x:1,y:1},w=Kt(r.convertOffsetParentRelativeRectToViewportRelativeRect?await r.convertOffsetParentRelativeRectToViewportRelativeRect({elements:o,rect:b,offsetParent:y,strategy:c}):b);return{top:(f.top-w.top+m.top)/v.y,bottom:(w.bottom-f.bottom+m.bottom)/v.y,left:(f.left-w.left+m.left)/v.x,right:(w.right-f.right+m.right)/v.x}}const Yt=new Set(["left","top"]);function Zt(){return"undefined"!=typeof window}function Xt(e){return ts(e)?(e.nodeName||"").toLowerCase():"#document"}function Qt(e){var t;return(null==e||null==(t=e.ownerDocument)?void 0:t.defaultView)||window}function es(e){var t;return null==(t=(ts(e)?e.ownerDocument:e.document)||window.document)?void 0:t.documentElement}function ts(e){return!!Zt()&&(e instanceof Node||e instanceof Qt(e).Node)}function ss(e){return!!Zt()&&(e instanceof Element||e instanceof Qt(e).Element)}function is(e){return!!Zt()&&(e instanceof HTMLElement||e instanceof Qt(e).HTMLElement)}function ns(e){return!(!Zt()||"undefined"==typeof ShadowRoot)&&(e instanceof ShadowRoot||e instanceof Qt(e).ShadowRoot)}const rs=new Set(["inline","contents"]);function as(e){const{overflow:t,overflowX:s,overflowY:i,display:n}=ys(e);return/auto|scroll|overlay|hidden|clip/.test(t+i+s)&&!rs.has(n)}const os=new Set(["table","td","th"]);function cs(e){return os.has(Xt(e))}const ls=[":popover-open",":modal"];function hs(e){return ls.some(t=>{try{return e.matches(t)}catch(e){return!1}})}const us=["transform","translate","scale","rotate","perspective"],ds=["transform","translate","scale","rotate","perspective","filter"],ps=["paint","layout","strict","content"];function ms(e){const t=gs(),s=ss(e)?ys(e):e;return us.some(e=>!!s[e]&&"none"!==s[e])||!!s.containerType&&"normal"!==s.containerType||!t&&!!s.backdropFilter&&"none"!==s.backdropFilter||!t&&!!s.filter&&"none"!==s.filter||ds.some(e=>(s.willChange||"").includes(e))||ps.some(e=>(s.contain||"").includes(e))}function gs(){return!("undefined"==typeof CSS||!CSS.supports)&&CSS.supports("-webkit-backdrop-filter","none")}const fs=new Set(["html","body","#document"]);function bs(e){return fs.has(Xt(e))}function ys(e){return Qt(e).getComputedStyle(e)}function vs(e){return ss(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function ws(e){if("html"===Xt(e))return e;const t=e.assignedSlot||e.parentNode||ns(e)&&e.host||es(e);return ns(t)?t.host:t}function Ts(e){const t=ws(e);return bs(t)?e.ownerDocument?e.ownerDocument.body:e.body:is(t)&&as(t)?t:Ts(t)}function Ss(e,t,s){var i;void 0===t&&(t=[]),void 0===s&&(s=!0);const n=Ts(e),r=n===(null==(i=e.ownerDocument)?void 0:i.body),a=Qt(n);if(r){const e=Cs(a);return t.concat(a,a.visualViewport||[],as(n)?n:[],e&&s?Ss(e):[])}return t.concat(n,Ss(n,[],s))}function Cs(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function Os(e){const t=ys(e);let s=parseFloat(t.width)||0,i=parseFloat(t.height)||0;const n=is(e),r=n?e.offsetWidth:s,a=n?e.offsetHeight:i,o=Et(s)!==r||Et(i)!==a;return o&&(s=r,i=a),{width:s,height:i,$:o}}function As(e){return ss(e)?e:e.contextElement}function Es(e){const t=As(e);if(!is(t))return Mt(1);const s=t.getBoundingClientRect(),{width:i,height:n,$:r}=Os(t);let a=(r?Et(s.width):s.width)/i,o=(r?Et(s.height):s.height)/n;return a&&Number.isFinite(a)||(a=1),o&&Number.isFinite(o)||(o=1),{x:a,y:o}}const xs=Mt(0);function Ms(e){const t=Qt(e);return gs()&&t.visualViewport?{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}:xs}function ks(e,t,s,i){void 0===t&&(t=!1),void 0===s&&(s=!1);const n=e.getBoundingClientRect(),r=As(e);let a=Mt(1);t&&(i?ss(i)&&(a=Es(i)):a=Es(e));const o=function(e,t,s){return void 0===t&&(t=!1),!(!s||t&&s!==Qt(e))&&t}(r,s,i)?Ms(r):Mt(0);let c=(n.left+o.x)/a.x,l=(n.top+o.y)/a.y,h=n.width/a.x,u=n.height/a.y;if(r){const e=Qt(r),t=i&&ss(i)?Qt(i):i;let s=e,n=Cs(s);for(;n&&i&&t!==s;){const e=Es(n),t=n.getBoundingClientRect(),i=ys(n),r=t.left+(n.clientLeft+parseFloat(i.paddingLeft))*e.x,a=t.top+(n.clientTop+parseFloat(i.paddingTop))*e.y;c*=e.x,l*=e.y,h*=e.x,u*=e.y,c+=r,l+=a,s=Qt(n),n=Cs(s)}}return Kt({width:h,height:u,x:c,y:l})}function Is(e,t){const s=vs(e).scrollLeft;return t?t.left+s:ks(es(e)).left+s}function Ls(e,t,s){void 0===s&&(s=!1);const i=e.getBoundingClientRect();return{x:i.left+t.scrollLeft-(s?0:Is(e,i)),y:i.top+t.scrollTop}}const _s=new Set(["absolute","fixed"]);function Ns(e,t,s){let i;if("viewport"===t)i=function(e,t){const s=Qt(e),i=es(e),n=s.visualViewport;let r=i.clientWidth,a=i.clientHeight,o=0,c=0;if(n){r=n.width,a=n.height;const e=gs();(!e||e&&"fixed"===t)&&(o=n.offsetLeft,c=n.offsetTop)}return{width:r,height:a,x:o,y:c}}(e,s);else if("document"===t)i=function(e){const t=es(e),s=vs(e),i=e.ownerDocument.body,n=At(t.scrollWidth,t.clientWidth,i.scrollWidth,i.clientWidth),r=At(t.scrollHeight,t.clientHeight,i.scrollHeight,i.clientHeight);let a=-s.scrollLeft+Is(e);const o=-s.scrollTop;return"rtl"===ys(i).direction&&(a+=At(t.clientWidth,i.clientWidth)-n),{width:n,height:r,x:a,y:o}}(es(e));else if(ss(t))i=function(e,t){const s=ks(e,!0,"fixed"===t),i=s.top+e.clientTop,n=s.left+e.clientLeft,r=is(e)?Es(e):Mt(1);return{width:e.clientWidth*r.x,height:e.clientHeight*r.y,x:n*r.x,y:i*r.y}}(t,s);else{const s=Ms(e);i={x:t.x-s.x,y:t.y-s.y,width:t.width,height:t.height}}return Kt(i)}function Ps(e,t){const s=ws(e);return!(s===t||!ss(s)||bs(s))&&("fixed"===ys(s).position||Ps(s,t))}function Fs(e,t,s){const i=is(t),n=es(t),r="fixed"===s,a=ks(e,!0,r,t);let o={scrollLeft:0,scrollTop:0};const c=Mt(0);function l(){c.x=Is(n)}if(i||!i&&!r)if(("body"!==Xt(t)||as(n))&&(o=vs(t)),i){const e=ks(t,!0,r,t);c.x=e.x+t.clientLeft,c.y=e.y+t.clientTop}else n&&l();r&&!i&&n&&l();const h=!n||i||r?Mt(0):Ls(n,o);return{x:a.left+o.scrollLeft-c.x-h.x,y:a.top+o.scrollTop-c.y-h.y,width:a.width,height:a.height}}function Rs(e){return"static"===ys(e).position}function Ds(e,t){if(!is(e)||"fixed"===ys(e).position)return null;if(t)return t(e);let s=e.offsetParent;return es(e)===s&&(s=s.ownerDocument.body),s}function js(e,t){const s=Qt(e);if(hs(e))return s;if(!is(e)){let t=ws(e);for(;t&&!bs(t);){if(ss(t)&&!Rs(t))return t;t=ws(t)}return s}let i=Ds(e,t);for(;i&&cs(i)&&Rs(i);)i=Ds(i,t);return i&&bs(i)&&Rs(i)&&!ms(i)?s:i||function(e){let t=ws(e);for(;is(t)&&!bs(t);){if(ms(t))return t;if(hs(t))return null;t=ws(t)}return null}(e)||s}const Bs={convertOffsetParentRelativeRectToViewportRelativeRect:function(e){let{elements:t,rect:s,offsetParent:i,strategy:n}=e;const r="fixed"===n,a=es(i),o=!!t&&hs(t.floating);if(i===a||o&&r)return s;let c={scrollLeft:0,scrollTop:0},l=Mt(1);const h=Mt(0),u=is(i);if((u||!u&&!r)&&(("body"!==Xt(i)||as(a))&&(c=vs(i)),is(i))){const e=ks(i);l=Es(i),h.x=e.x+i.clientLeft,h.y=e.y+i.clientTop}const d=!a||u||r?Mt(0):Ls(a,c,!0);return{width:s.width*l.x,height:s.height*l.y,x:s.x*l.x-c.scrollLeft*l.x+h.x+d.x,y:s.y*l.y-c.scrollTop*l.y+h.y+d.y}},getDocumentElement:es,getClippingRect:function(e){let{element:t,boundary:s,rootBoundary:i,strategy:n}=e;const r=[..."clippingAncestors"===s?hs(t)?[]:function(e,t){const s=t.get(e);if(s)return s;let i=Ss(e,[],!1).filter(e=>ss(e)&&"body"!==Xt(e)),n=null;const r="fixed"===ys(e).position;let a=r?ws(e):e;for(;ss(a)&&!bs(a);){const t=ys(a),s=ms(a);s||"fixed"!==t.position||(n=null),(r?!s&&!n:!s&&"static"===t.position&&n&&_s.has(n.position)||as(a)&&!s&&Ps(e,a))?i=i.filter(e=>e!==a):n=t,a=ws(a)}return t.set(e,i),i}(t,this._c):[].concat(s),i],a=r[0],o=r.reduce((e,s)=>{const i=Ns(t,s,n);return e.top=At(i.top,e.top),e.right=Ot(i.right,e.right),e.bottom=Ot(i.bottom,e.bottom),e.left=At(i.left,e.left),e},Ns(t,a,n));return{width:o.right-o.left,height:o.bottom-o.top,x:o.left,y:o.top}},getOffsetParent:js,getElementRects:async function(e){const t=this.getOffsetParent||js,s=this.getDimensions,i=await s(e.floating);return{reference:Fs(e.reference,await t(e.floating),e.strategy),floating:{x:0,y:0,width:i.width,height:i.height}}},getClientRects:function(e){return Array.from(e.getClientRects())},getDimensions:function(e){const{width:t,height:s}=Os(e);return{width:t,height:s}},getScale:Es,isElement:ss,isRTL:function(e){return"rtl"===ys(e).direction}};function $s(e,t){return e.x===t.x&&e.y===t.y&&e.width===t.width&&e.height===t.height}const Vs=function(e){return void 0===e&&(e=0),{name:"offset",options:e,async fn(t){var s,i;const{x:n,y:r,placement:a,middlewareData:o}=t,c=await async function(e,t){const{placement:s,platform:i,elements:n}=e,r=await(null==i.isRTL?void 0:i.isRTL(n.floating)),a=Nt(s),o=Pt(s),c="y"===jt(s),l=Yt.has(a)?-1:1,h=r&&c?-1:1,u=_t(t,e);let{mainAxis:d,crossAxis:p,alignmentAxis:m}="number"==typeof u?{mainAxis:u,crossAxis:0,alignmentAxis:null}:{mainAxis:u.mainAxis||0,crossAxis:u.crossAxis||0,alignmentAxis:u.alignmentAxis};return o&&"number"==typeof m&&(p="end"===o?-1*m:m),c?{x:p*h,y:d*l}:{x:d*l,y:p*h}}(t,e);return a===(null==(s=o.offset)?void 0:s.placement)&&null!=(i=o.arrow)&&i.alignmentOffset?{}:{x:n+c.x,y:r+c.y,data:{...c,placement:a}}}}},qs=function(e){return void 0===e&&(e={}),{name:"shift",options:e,async fn(t){const{x:s,y:i,placement:n}=t,{mainAxis:r=!0,crossAxis:a=!1,limiter:o={fn:e=>{let{x:t,y:s}=e;return{x:t,y:s}}},...c}=_t(e,t),l={x:s,y:i},h=await Jt(t,c),u=jt(Nt(n)),d=Ft(u);let p=l[d],m=l[u];if(r){const e="y"===d?"bottom":"right";p=Lt(p+h["y"===d?"top":"left"],p,p-h[e])}if(a){const e="y"===u?"bottom":"right";m=Lt(m+h["y"===u?"top":"left"],m,m-h[e])}const g=o.fn({...t,[d]:p,[u]:m});return{...g,data:{x:g.x-s,y:g.y-i,enabled:{[d]:r,[u]:a}}}}}},Us=function(e){return void 0===e&&(e={}),{name:"flip",options:e,async fn(t){var s,i;const{placement:n,middlewareData:r,rects:a,initialPlacement:o,platform:c,elements:l}=t,{mainAxis:h=!0,crossAxis:u=!0,fallbackPlacements:d,fallbackStrategy:p="bestFit",fallbackAxisSideDirection:m="none",flipAlignment:g=!0,...f}=_t(e,t);if(null!=(s=r.arrow)&&s.alignmentOffset)return{};const b=Nt(n),y=jt(o),v=Nt(o)===o,w=await(null==c.isRTL?void 0:c.isRTL(l.floating)),T=d||(v||!g?[Ht(o)]:function(e){const t=Ht(e);return[Vt(e),t,Vt(t)]}(o)),S="none"!==m;!d&&S&&T.push(...function(e,t,s,i){const n=Pt(e);let r=function(e,t,s){switch(e){case"top":case"bottom":return s?t?Ut:qt:t?qt:Ut;case"left":case"right":return t?zt:Wt;default:return[]}}(Nt(e),"start"===s,i);return n&&(r=r.map(e=>e+"-"+n),t&&(r=r.concat(r.map(Vt)))),r}(o,g,m,w));const C=[o,...T],O=await Jt(t,f),A=[];let E=(null==(i=r.flip)?void 0:i.overflows)||[];if(h&&A.push(O[b]),u){const e=$t(n,a,w);A.push(O[e[0]],O[e[1]])}if(E=[...E,{placement:n,overflows:A}],!A.every(e=>e<=0)){var x,M;const e=((null==(x=r.flip)?void 0:x.index)||0)+1,t=C[e];if(t&&("alignment"!==u||y===jt(t)||E.every(e=>jt(e.placement)!==y||e.overflows[0]>0)))return{data:{index:e,overflows:E},reset:{placement:t}};let s=null==(M=E.filter(e=>e.overflows[0]<=0).sort((e,t)=>e.overflows[1]-t.overflows[1])[0])?void 0:M.placement;if(!s)switch(p){case"bestFit":{var k;const e=null==(k=E.filter(e=>{if(S){const t=jt(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:k[0];e&&(s=e);break}case"initialPlacement":s=o}if(n!==s)return{reset:{placement:s}}}return{}}}},zs=e=>{Object.assign(e,{show(){this.cancelBehaviourOpen?.(),this.openValue=!0},hide(){this.openValue=!1},toggle(){this.cancelBehaviourOpen?.(),this.openValue=!this.openValue},setupFloatingUI({trigger:e,popover:t,strategy:s}){this.floatingUICleanup=function(e,t,s,i){void 0===i&&(i={});const{ancestorScroll:n=!0,ancestorResize:r=!0,elementResize:a="function"==typeof ResizeObserver,layoutShift:o="function"==typeof IntersectionObserver,animationFrame:c=!1}=i,l=As(e),h=n||r?[...l?Ss(l):[],...Ss(t)]:[];h.forEach(e=>{n&&e.addEventListener("scroll",s,{passive:!0}),r&&e.addEventListener("resize",s)});const u=l&&o?function(e,t){let s,i=null;const n=es(e);function r(){var e;clearTimeout(s),null==(e=i)||e.disconnect(),i=null}return function a(o,c){void 0===o&&(o=!1),void 0===c&&(c=1),r();const l=e.getBoundingClientRect(),{left:h,top:u,width:d,height:p}=l;if(o||t(),!d||!p)return;const m={rootMargin:-xt(u)+"px "+-xt(n.clientWidth-(h+d))+"px "+-xt(n.clientHeight-(u+p))+"px "+-xt(h)+"px",threshold:At(0,Ot(1,c))||1};let g=!0;function f(t){const i=t[0].intersectionRatio;if(i!==c){if(!g)return a();i?a(!1,i):s=setTimeout(()=>{a(!1,1e-7)},1e3)}1!==i||$s(l,e.getBoundingClientRect())||a(),g=!1}try{i=new IntersectionObserver(f,{...m,root:n.ownerDocument})}catch(e){i=new IntersectionObserver(f,m)}i.observe(e)}(!0),r}(l,s):null;let d,p=-1,m=null;a&&(m=new ResizeObserver(e=>{let[i]=e;i&&i.target===l&&m&&(m.unobserve(t),cancelAnimationFrame(p),p=requestAnimationFrame(()=>{var e;null==(e=m)||e.observe(t)})),s()}),l&&!c&&m.observe(l),m.observe(t));let g=c?ks(e):null;return c&&function t(){const i=ks(e);g&&!$s(g,i)&&s(),g=i,d=requestAnimationFrame(t)}(),s(),()=>{var e;h.forEach(e=>{n&&e.removeEventListener("scroll",s),r&&e.removeEventListener("resize",s)}),null==u||u(),null==(e=m)||e.disconnect(),m=null,c&&cancelAnimationFrame(d)}}(e,t,()=>{((e,t,s)=>{const i=new Map,n={platform:Bs,...s},r={...n.platform,_c:i};return(async(e,t,s)=>{const{placement:i="bottom",strategy:n="absolute",middleware:r=[],platform:a}=s,o=r.filter(Boolean),c=await(null==a.isRTL?void 0:a.isRTL(t));let l=await a.getElementRects({reference:e,floating:t,strategy:n}),{x:h,y:u}=Gt(l,i,c),d=i,p={},m=0;for(let s=0;s{const n={left:`${e}px`,top:`${s}px`,position:i};Object.assign(t.style,n)})})},openValueChanged(){this.disabledValue||(this.openValue?(this.preparePopoverOpenAnimation?.(),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()))}})},Ws=class extends i.xI{static targets=["button","popover"];static values={placement:{type:String,default:"bottom-end"},open:{type:Boolean,default:!1},autoPlacement:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1},size:{type:Number,default:24},perLine:{type:Number,default:9}};initialize(){this.onEmojiSelect=this.onEmojiSelect.bind(this),this.pickerLoaded=!1,this.pickerLoadPromise=null,this.connected=!1,super.initialize()}connect(){this.connected=!0,zs(this),this.setupFloatingUI({trigger:this.buttonTarget,popover:this.popoverTarget,strategy:"absolute"}),super.connect()}disconnect(){this.connected=!1,this.pickerLoadPromise=null,this.floatingUICleanup(),super.disconnect()}onEmojiSelect(e){this.dispatch("selected",{detail:e.native}),this.hide()}onClickOutside(e){this.openValue&&e.target.nodeType&&!1===this.element.contains(e.target)&&(this.openValue=!1)}async onPopoverOpened(){await this.loadPicker()}async loadPicker(){if(this.pickerLoaded)return;this.pickerLoadPromise||=this.loadPickerDependencies();const{Picker:e,i18n:t}=await this.pickerLoadPromise;this.connected&&!this.pickerLoaded&&(this.popoverTarget.appendChild(this.buildPicker(e,t)),this.pickerLoaded=!0)}async loadPickerDependencies(){const[e,t]=await Promise.all([s.e(160).then(s.bind(s,405)),this.loadI18n()]);return{Picker:e.Picker,i18n:t.default||t}}loadI18n(){return"es"===Hellotext.business.locale?s.e(437).then(s.t.bind(s,9,19)):s.e(200).then(s.t.bind(s,714,19))}buildPicker(e,t){return new e({onEmojiSelect:this.onEmojiSelect,theme:"light",dynamicWidth:!0,previewPosition:"none",skinTonePosition:"none",emojiSize:this.sizeValue,perLine:this.perLineValue,i18n:t})}get middlewares(){return[Vs(5),qs({padding:24}),(e={allowedPlacements:["top","bottom"]},void 0===e&&(e={}),{name:"autoPlacement",options:e,async fn(t){var s,i,n;const{rects:r,middlewareData:a,placement:o,platform:c,elements:l}=t,{crossAxis:h=!1,alignment:u,allowedPlacements:d=Ct,autoAlignment:p=!0,...m}=_t(e,t),g=void 0!==u||d===Ct?function(e,t,s){return(e?[...s.filter(t=>Pt(t)===e),...s.filter(t=>Pt(t)!==e)]:s.filter(e=>Nt(e)===e)).filter(s=>!e||Pt(s)===e||!!t&&Vt(s)!==s)}(u||null,p,d):d,f=await Jt(t,m),b=(null==(s=a.autoPlacement)?void 0:s.index)||0,y=g[b];if(null==y)return{};const v=$t(y,r,await(null==c.isRTL?void 0:c.isRTL(l.floating)));if(o!==y)return{reset:{placement:g[0]}};const w=[f[Nt(y)],f[v[0]],f[v[1]]],T=[...(null==(i=a.autoPlacement)?void 0:i.overflows)||[],{placement:y,overflows:w}],S=g[b+1];if(S)return{data:{index:b+1,overflows:T},reset:{placement:S}};const C=T.map(e=>{const t=Pt(e.placement);return[e.placement,t&&h?e.overflows.slice(0,2).reduce((e,t)=>e+t,0):e.overflows[0],e.overflows]}).sort((e,t)=>e[1]-t[1]),O=(null==(n=C.filter(e=>e[2].slice(0,Pt(e[0])?2:3).every(e=>e<=0))[0])?void 0:n[0])||C[0][0];return O!==o?{data:{index:b+1,overflows:T},reset:{placement:O}}:{}}})];var e}};class Hs{static get endpoint(){return g.endpoint("public/webchats/:id/messages")}constructor(e){this.webchatId=e}async index(e){const t=new URL(this.url);return Object.entries(e).forEach(([e,s])=>{t.searchParams.append(e,s)}),await fetch(t,{method:"GET",headers:bt.headers})}catchUp(e){return this.index({after_id:e,session:bt.session})}async create(e){const t=await fetch(this.url,{method:"POST",headers:{Authorization:`Bearer ${bt.business.id}`},body:e});return new y(t.ok,t)}markAsSeen(e=null){const t=e?this.url+`/${e}`:this.url+"/seen";fetch(t,{method:"PATCH",headers:bt.headers,body:JSON.stringify({session:bt.session})})}get url(){return Hs.endpoint.replace(":id",this.webchatId)}}const Ks=Hs;class Gs{static webSocket;static channels=new Set;static messageHandlers=new Set;static disconnectHandlers=new Set;static subscriptionConfirmHandlers=new Set;static reconnectTimeout=null;static reconnectAttempts=0;static reconnectBaseDelay=500;static reconnectMaxDelay=1e4;static reconnectJitter=.3;static needsResubscribe=!1;constructor(){Gs.channels.add(this)}send({command:e,identifier:t,data:s}){const i={command:e,identifier:JSON.stringify(t),data:JSON.stringify(s||{})},n=Gs.ensureWebSocket(),r=JSON.stringify(i);n.readyState===WebSocket.OPEN?n.send(r):n.addEventListener("open",()=>{n.send(r)})}onMessage(e){const t=t=>{const s=JSON.parse(t.data),{type:i,message:n}=s;this.ignoredEvents.includes(i)||e(n)};Gs.messageHandlers.add(t),Gs.ensureWebSocket().addEventListener("message",t)}onDisconnect(e){Gs.disconnectHandlers.add(e)}onSubscriptionConfirmed(e){Gs.subscriptionConfirmHandlers.add(e)}get webSocket(){return Gs.ensureWebSocket()}static ensureWebSocket(){return this.webSocket&&!this.closedWebSocket(this.webSocket)?this.webSocket:(this.webSocket&&(this.needsResubscribe=!0),this.openWebSocket())}static openWebSocket(){this.clearReconnectTimeout();const e=new WebSocket(g.actionCableUrl);return this.webSocket=e,this.installWebSocketHandlers(e),e}static installWebSocketHandlers(e){e.addEventListener("open",()=>this.handleOpen(e)),e.addEventListener("close",()=>this.handleDisconnect(e)),e.addEventListener("error",()=>this.handleDisconnect(e)),e.addEventListener("message",e=>this.handleControlMessage(e)),this.messageHandlers.forEach(t=>{e.addEventListener("message",t)})}static handleOpen(e){e===this.webSocket&&(this.reconnectAttempts=0,this.needsResubscribe&&(this.needsResubscribe=!1,this.resubscribeChannels()))}static handleControlMessage(e){let t;try{t=JSON.parse(e.data)}catch{return}"confirm_subscription"===t.type&&this.subscriptionConfirmHandlers.forEach(e=>e(t.identifier))}static handleDisconnect(e){e===this.webSocket&&(this.disconnectHandlers.forEach(e=>e()),this.webSocket=null,this.needsResubscribe=!0,this.scheduleReconnect())}static scheduleReconnect(){this.reconnectTimeout||(this.reconnectTimeout=setTimeout(()=>{this.reconnectTimeout=null,this.reconnectAttempts+=1,this.openWebSocket()},this.reconnectDelay))}static clearReconnectTimeout(){this.reconnectTimeout&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null)}static resubscribeChannels(){this.channels.forEach(e=>{const t=e.resubscribe||e.subscribe;"function"==typeof t&&t.call(e)})}static closedWebSocket(e){return e.readyState===WebSocket.CLOSED||e.readyState===WebSocket.CLOSING}static get reconnectDelay(){const e=Math.min(this.reconnectMaxDelay,this.reconnectBaseDelay*2**this.reconnectAttempts);return e+Math.round(e*this.reconnectJitter*Math.random())}get ignoredEvents(){return["ping","confirm_subscription","welcome"]}}const Js=Gs,Ys=class extends Js{constructor(e,t,s){super(),this.id=e,this.session=t,this.conversation=s,this.subscribed=!1,this.awaitingReconnectConfirmation=!1,this.reconnectCallbacks=new Set,this.onSubscriptionConfirmed(e=>this.handleSubscriptionConfirmed(e)),this.subscribe()}subscribe(){this.subscribed=!0;const e={channel:"WebchatChannel",id:this.id,session:this.session,conversation:this.conversation};this.send({command:"subscribe",identifier:e})}unsubscribe(){this.subscribed=!1;const e={channel:"WebchatChannel",id:this.id,session:this.session,conversation:this.conversation};this.send({command:"unsubscribe",identifier:e})}resubscribe(){!1!==this.subscribed&&(this.awaitingReconnectConfirmation=!0,this.subscribe())}onReconnect(e){this.reconnectCallbacks.add(e)}handleSubscriptionConfirmed(e){this.awaitingReconnectConfirmation&&this.matchesIdentifier(e)&&(this.awaitingReconnectConfirmation=!1,this.reconnectCallbacks.forEach(e=>e()))}matchesIdentifier(e){let t;try{t="string"==typeof e?JSON.parse(e):e}catch{return!1}return"WebchatChannel"===t.channel&&t.id===this.id&&t.session===this.session&&t.conversation===this.conversation}startTypingIndicator(){const e={channel:"WebchatChannel",id:this.id,session:this.session,conversation:this.conversation};this.send({command:"message",identifier:e,data:{action:"started_typing"}})}stopTypingIndicator(){const e={channel:"WebchatChannel",id:this.id,session:this.session,conversation:this.conversation};this.send({command:"typing:stop",identifier:e,data:{action:"stopped_typing"}})}onMessage(e){super.onMessage(t=>{"message"===t.type&&e(t)})}onReaction(e){super.onMessage(t=>{"reaction.create"!==t.type&&"reaction.destroy"!==t.type||e(t)})}onTypingStart(e){super.onMessage(t=>{"started_typing"===t.type&&e(t)})}updateSubscriptionWith(e){this.unsubscribe(),setTimeout(()=>{this.conversation=e,this.subscribe()},1e3)}},Zs=e=>{Object.assign(e,{scheduleBehaviourOpen(){if(!this.shouldAutoOpenFromBehaviour())return;const 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(){const 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--${this.idValue}--auto-opened`},sessionKey(){return`hellotext--webchat--${this.idValue}--auto-opened-session`}})},Xs=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){const t=this.openingSequenceMessages[e];if(!t)return;const s=1e3*this.openingSequenceMessageDelay(t);this.openingSequenceTimeout=window.setTimeout(()=>{this.openingSequenceTimeout=null,this.openingSequenceCancelled||(this.revealOpeningSequenceMessage(t),this.playOpeningSequenceMessageAt(e+1))},s)},revealOpeningSequenceMessage(e){this.messagesContainerTarget.insertBefore(e,this.messageTemplateTarget),e.hidden=!1,this.recordOpeningSequenceMessage(e),this.scrollOpeningSequenceToBottom()},recordOpeningSequenceMessage(e){const t=e.dataset.openingSequenceMessageId;t&&!this.revealedOpeningSequenceMessageIds.includes(t)&&this.revealedOpeningSequenceMessageIds.push(t)},openingSequenceMessageDelay(e){const 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=[]}})},Qs=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){const t=e+1;if(t>=this.teaserMessages.length)return;const s=this.teaserMessages[e],i=this.teaserPresentationDelay(s);this.teaserCycleTimeout=window.setTimeout(()=>{this.teaserCycleTimeout=null,this.showTeaserMessage(t),this.scheduleNextTeaserMessage(t)},i)},showTeaserMessage(e){this.teaserMessages.forEach((t,s)=>{t.classList.toggle("hidden",s!==e)})},stopTeaserCycle(){null!==this.teaserCycleTimeout&&void 0!==this.teaserCycleTimeout&&(window.clearTimeout(this.teaserCycleTimeout),this.teaserCycleTimeout=null)},teaserMessageDelay(e){const t=Number(e.dataset.delaySeconds||0);return Number.isFinite(t)?t:0},teaserPresentationDelay(e){return Math.max(1e3*this.teaserMessageDelay(e),250)},hasRenderedConversationMessages(){let e=[];try{e=Array.from(this.messageTargets||[])}catch(t){e=[]}return e.some(e=>e!==this.messageTemplateTarget)},teaserSeenKey(){const e=this.hasTeaserTarget?this.teaserTarget.dataset.teaserVersion:"",t=e?`:${e}`:"";return`hellotext:webchat:${this.idValue||this.element.id}:teaser-seen${t}`},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())}})},ei={hour:"numeric",minute:"2-digit"},ti=/Android|iPhone|iPad|iPod/i,si={capture:!0,passive:!0},ii=class extends i.xI{static messageTimestampFormatters={};static 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};static classes=["fadeOut"];static targets=["trigger","popover","input","attachmentInput","attachmentButton","errorMessageContainer","attachmentTemplate","attachmentContainer","attachment","messageTemplate","messagesContainer","title","attachmentImage","footer","toolbar","message","unreadCounter","typingIndicator","typingIndicatorTemplate","teaser","teaserMessage","inboundMessageTeaser","inboundMessageTeaserBody","openingSequence","openingSequenceMessage"];initialize(){this.messagesAPI=new Ks(this.idValue),this.webChatChannel=new Ys(this.idValue,bt.session,this.conversationIdValue),this.files=[],this.messageIds=new Set,this.catchUpAfterMessageId=null,this.fetchingCatchUpMessages=!1,this.onMessageReceived=this.onMessageReceived.bind(this),this.onMessageReaction=this.onMessageReaction.bind(this),this.onTypingStart=this.onTypingStart.bind(this),this.captureCatchUpCursor=this.captureCatchUpCursor.bind(this),this.catchUpMessages=this.catchUpMessages.bind(this),this.onScroll=this.onScroll.bind(this),this.onOutboundMessageSent=this.onOutboundMessageSent.bind(this),this.closePopoverOnEscape=this.closePopoverOnEscape.bind(this),this.broadcastChannel=new BroadcastChannel(`hellotext--webchat--${this.idValue}`),this.webChatChannel.onDisconnect(this.captureCatchUpCursor),this.webChatChannel.onReconnect(this.catchUpMessages),super.initialize()}connect(){Zs(this),zs(this),Xs(this),Qs(this),this.setupFloatingUI({trigger:this.triggerTarget,popover:this.popoverTarget}),this.hasTeaserTarget&&this.setupFloatingUI({trigger:this.triggerTarget,popover:this.teaserTarget,strategy:"absolute"}),this.setupTeaser(),this.setupOpeningSequence(),this.localizeMessageTimestamps(),this.webChatChannel.onMessage(this.onMessageReceived),this.webChatChannel.onTypingStart(this.onTypingStart),this.webChatChannel.onReaction(this.onMessageReaction),this.setupMessagesContainerScrollIsolation(),this.messagesContainerTarget.addEventListener("scroll",this.onScroll),this.messagesContainerTarget.addEventListener("wheel",this.stopHostScrollPropagation,si),this.messagesContainerTarget.addEventListener("touchmove",this.stopHostScrollPropagation,si),this.shouldOpenOnMount&&(this.openValue=!0),bt.eventEmitter.dispatch("webchat:mounted"),this.broadcastChannel.addEventListener("message",this.onOutboundMessageSent),window.addEventListener("keydown",this.closePopoverOnEscape,!0),this.scheduleBehaviourOpen(),super.connect()}disconnect(){this.cancelBehaviourOpen(),this.clearPopoverOpenAnimation(),this.teardownTeaser(),this.teardownOpeningSequence(),this.broadcastChannel.removeEventListener("message",this.onOutboundMessageSent),this.messagesContainerTarget.removeEventListener("scroll",this.onScroll),this.messagesContainerTarget.removeEventListener("wheel",this.stopHostScrollPropagation,si),this.messagesContainerTarget.removeEventListener("touchmove",this.stopHostScrollPropagation,si),window.removeEventListener("keydown",this.closePopoverOnEscape,!0),this.clearTypingIndicator(),this.broadcastChannel.close(),this.floatingUICleanup(),super.disconnect()}setupMessagesContainerScrollIsolation(){this.messagesContainerTarget.style.overscrollBehavior="contain",this.messagesContainerTarget.style.webkitOverflowScrolling="touch",this.messagesContainerTarget.style.touchAction="pan-y",this.messagesContainerTarget.setAttribute("data-lenis-prevent",""),this.messagesContainerTarget.setAttribute("data-lenis-prevent-wheel",""),this.messagesContainerTarget.setAttribute("data-lenis-prevent-touch","")}stopHostScrollPropagation(e){e.stopPropagation()}onTypingStart(){if(this.typingIndicatorVisible)return this.resetTypingIndicatorTimer();this.showTypingIndicator()}showOptimisticTypingIndicator(){this.typingIndicatorVisible||this.showTypingIndicator()}showTypingIndicator(){this.clearTypingIndicator(),this.typingIndicatorVisible=!0;const e=this.typingIndicatorTemplateTarget.cloneNode(!0);e.setAttribute("data-hellotext--webchat-target","typingIndicator"),e.style.display="flex",this.messagesContainerTarget.appendChild(e),requestAnimationFrame(()=>{this.messagesContainerTarget.scroll({top:this.messagesContainerTarget.scrollHeight,behavior:"instant"})});const t=this.typingIndicatorKeepAliveValue;this.incomingTypingIndicatorTimeout=setTimeout(()=>{this.clearTypingIndicator()},t)}resetTypingIndicatorTimer(){if(!this.typingIndicatorVisible)return;clearTimeout(this.incomingTypingIndicatorTimeout),clearTimeout(this.optimisticTypingTimeout);const e=this.typingIndicatorKeepAliveValue;this.incomingTypingIndicatorTimeout=setTimeout(()=>{this.clearTypingIndicator()},e)}clearTypingIndicator(){this.hasTypingIndicatorTarget&&this.typingIndicatorTarget.remove(),this.typingIndicatorVisible=!1,clearTimeout(this.incomingTypingIndicatorTimeout),clearTimeout(this.optimisticTypingTimeout)}onMessageInputChange(){this.resizeInput(),clearTimeout(this.typingIndicatorTimeout),this.hasSentTypingIndicator||(this.webChatChannel.startTypingIndicator(),this.hasSentTypingIndicator=!0),this.typingIndicatorTimeout=setTimeout(()=>{this.hasSentTypingIndicator=!1},3e3)}onOutboundMessageSent(e){const{data:t}=e,s={"message:sent":e=>{const t=(new DOMParser).parseFromString(e.element,"text/html").body.firstElementChild;this.localizeMessageTimestamps(t),this.typingIndicatorVisible&&this.hasTypingIndicatorTarget?this.messagesContainerTarget.insertBefore(t,this.typingIndicatorTarget):this.messagesContainerTarget.appendChild(t),t.scrollIntoView({behavior:"instant"})},"message:failed":e=>{const t=this.messagesContainerTarget.querySelector(`#${e.id}`);this.markMessageFailed(t,e.reason)}};s[t.type]?s[t.type](t):console.log(`Unhandled message event: ${t.type}`)}async onScroll(){if(this.messagesContainerTarget.scrollTop>300||!this.nextPageValue||this.fetchingNextPage)return;this.fetchingNextPage=!0;const e=await this.messagesAPI.index({page:this.nextPageValue,session:bt.session}),{next:t,messages:s}=await e.json();this.nextPageValue=t,this.oldScrollHeight=this.messagesContainerTarget.scrollHeight,s.forEach(e=>{const{body:t,attachments:s}=e,i=e.created_at||e.createdAt,n=this.messageTemplateTarget.cloneNode(!0);n.classList.add("hellotext--webchat-message"),n.setAttribute("data-hellotext--webchat-target","message"),n.setAttribute("data-id",e.id),i&&n.setAttribute("data-created-at",i),n.style.removeProperty("display"),nt(n.querySelector("[data-body]"),t),"received"===e.state?n.classList.add("received"):n.classList.remove("received"),s&&s.forEach(e=>{const t=this.attachmentImageTarget.cloneNode(!0);t.removeAttribute("data-hellotext--webchat-target"),t.src=e,t.style.display="block",this.messageAttachmentsContainer(n)?.appendChild(t)}),n.setAttribute("data-body",t),this.localizeMessageTimestamp(n.querySelector("[data-message-timestamp]"),i),this.messagesContainerTarget.prepend(n)}),this.messagesContainerTarget.scroll({top:this.messagesContainerTarget.scrollHeight-this.oldScrollHeight,behavior:"instant"}),this.fetchingNextPage=!1}onClickOutside(e){u.mode===h.POPOVER&&this.openValue&&e.target.nodeType&&!1===this.element.contains(e.target)&&(this.openValue=!1)}closePopover(){this.clearPopoverOpenAnimation(),this.popoverTarget.classList.remove(...this.fadeOutClasses),this.openValue=!1}preparePopoverOpenAnimation(){this.clearPopoverOpenAnimation(),this.popoverTarget.classList.remove(...this.fadeOutClasses),this.popoverTarget.classList.add("hellotext--webchat-popover-opening"),this.popoverOpenAnimationTimeout=setTimeout(()=>{this.popoverTarget.classList.remove("hellotext--webchat-popover-opening"),this.popoverOpenAnimationTimeout=null},120)}clearPopoverOpenAnimation(){this.popoverOpenAnimationTimeout&&(clearTimeout(this.popoverOpenAnimationTimeout),this.popoverOpenAnimationTimeout=null),this.popoverTarget?.classList.remove("hellotext--webchat-popover-opening")}onPopoverOpened(){this.popoverTarget.classList.remove(...this.fadeOutClasses),this.dismissTeaserForSession?.(),this.onMobile||this.focusComposeInput(),this.scrolled||(requestAnimationFrame(()=>{this.messagesContainerTarget.scroll({top:this.messagesContainerTarget.scrollHeight,behavior:"instant"})}),this.scrolled=!0),bt.eventEmitter.dispatch("webchat:opened"),localStorage.setItem(`hellotext--webchat--${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())}onPopoverClosed(){this.clearPopoverOpenAnimation(),bt.eventEmitter.dispatch("webchat:closed"),localStorage.setItem(`hellotext--webchat--${this.idValue}`,"closed")}onMessageReaction(e){const{message:t,reaction:s,type:i}=e,n=this.messageTargets.find(e=>e.dataset.id===t).querySelector("[data-reactions]");if("reaction.destroy"===i)return n.querySelector(`[data-id="${s.id}"]`).remove();if(n.querySelector(`[data-id="${s.id}"]`))n.querySelector(`[data-id="${s.id}"]`).innerText=s.emoji;else{const e=document.createElement("span");e.innerText=s.emoji,e.setAttribute("data-id",s.id),n.appendChild(e)}}onMessageReceived(e,t={}){const{id:s,body:i,attachments:n,teaser:r}=e,a=e.created_at||e.createdAt;if(!this.claimMessageId(s))return;if(this.hideTeaser?.(),e.carousel)return this.insertCarouselMessage(e,t);const o=this.messageTemplateTarget.cloneNode(!0);o.classList.add("hellotext--webchat-message"),o.style.display="flex",nt(o.querySelector("[data-body]"),i),o.setAttribute("data-id",s),o.setAttribute("data-hellotext--webchat-target","message"),this.setMessageCreatedAt(o,a),this.localizeMessageTimestamp(o.querySelector("[data-message-timestamp]"),a),n&&n.forEach(e=>{const t=this.attachmentImageTarget.cloneNode(!0);t.src=e,t.style.display="block",this.messageAttachmentsContainer(o)?.appendChild(t)}),this.clearTypingIndicator(),this.insertMessageElement(o),bt.eventEmitter.dispatch("webchat:message:received",{...e,body:o.querySelector("[data-body]").innerText}),!1!==t.scroll&&o.scrollIntoView({behavior:"smooth"}),this.updateMessageTeaser(r),this.openValue?this.messagesAPI.markAsSeen(s):this.incrementUnreadCounter()}claimMessageId(e){const t=this.messageTargets||[];return!this.messageIds.has(e)&&(this.messageIds.add(e),!t.some(t=>t.dataset.id===e))}captureCatchUpCursor(){this.catchUpAfterMessageId=this.lastRenderedMessageId}async catchUpMessages(){const e=this.catchUpAfterMessageId;if(e&&!this.fetchingCatchUpMessages){this.fetchingCatchUpMessages=!0;try{const t=await this.messagesAPI.catchUp(e),{messages:s=[]}=await t.json();s.forEach(e=>this.onMessageReceived(e,{scroll:!1})),this.catchUpAfterMessageId=this.lastRenderedMessageId}finally{this.fetchingCatchUpMessages=!1}}}get lastRenderedMessageId(){const e=this.persistedMessageElements;return e[e.length-1]?.dataset.id||null}get persistedMessageElements(){return Array.from(this.messagesContainerTarget.querySelectorAll(".hellotext--webchat-message[data-id]"))}setMessageCreatedAt(e,t){t&&e.setAttribute("data-created-at",t)}insertMessageElement(e){const t=this.nextMessageElementFor(e);t?this.messagesContainerTarget.insertBefore(e,t):this.messagesContainerTarget.appendChild(e)}nextMessageElementFor(e){const t=Date.parse(e.dataset.createdAt);return Number.isNaN(t)?null:this.persistedMessageElements.find(s=>{if(s===e)return!1;const i=Date.parse(s.dataset.createdAt);return!Number.isNaN(i)&&i>t})}updateMessageTeaser(e){this.messageTeaserValue=e,this.messageTeaserValue&&this.hasTeaserTarget&&this.hasInboundMessageTeaserTarget&&this.hasInboundMessageTeaserBodyTarget&&(this.teaserMessageTargets.forEach(e=>e.classList.add("hidden")),this.inboundMessageTeaserBodyTarget.textContent=this.messageTeaserValue,this.inboundMessageTeaserTarget.classList.remove("hidden"),this.teaserTarget.classList.toggle("invisible",this.openValue))}insertCarouselMessage(e,t={}){const s=e.html,i=e.created_at||e.createdAt,n=function(e){return it(e,st)}(s).firstElementChild;n.classList.add("hellotext--webchat-message"),n.setAttribute("data-id",e.id),n.setAttribute("data-hellotext--webchat-target","message"),this.setMessageCreatedAt(n,i),this.localizeMessageTimestamps(n),this.clearTypingIndicator(),this.insertMessageElement(n),!1!==t.scroll&&n.scrollIntoView({behavior:"smooth"}),bt.eventEmitter.dispatch("webchat:message:received",{...e,body:n.querySelector("[data-body]")?.innerText||""}),this.updateMessageTeaser(e.teaser),this.openValue?this.messagesAPI.markAsSeen(e.id):this.incrementUnreadCounter()}resizeInput(){this.inputTarget.style.height="auto";const e=this.inputTarget.scrollHeight;this.inputTarget.style.height=`${Math.min(e,96)}px`}async sendQuickReplyMessage({detail:{id:e,product:t,buttonId:s,body:i,cardElement:n}}){this.dismissTeaserForSession?.();const r=new FormData;r.append("message[body]",i),e&&r.append("message[replied_to]",e),t&&r.append("message[product]",t),s&&r.append("message[button]",s),r.append("session",bt.session),r.append("locale",o.toString()),this.appendOpeningSequenceMessageIds(r);const a=this.buildMessageElement(),c=n?.querySelector("img")?.cloneNode(!0);a.querySelector("[data-body]").innerText=i,c&&(c.removeAttribute("width"),c.removeAttribute("height"),this.messageAttachmentsContainer(a)?.appendChild(c)),this.typingIndicatorVisible&&this.hasTypingIndicatorTarget?this.messagesContainerTarget.insertBefore(a,this.typingIndicatorTarget):this.messagesContainerTarget.appendChild(a),a.scrollIntoView({behavior:"smooth"}),this.broadcastChannel.postMessage({type:"message:sent",element:a.outerHTML});const l=await this.messagesAPI.create(r);if(l.failed)return clearTimeout(this.optimisticTypingTimeout),this.markMessageFailedFromResponse(l,a);const h=await l.json();this.dispatch("set:id",{target:a,detail:h.id}),this.localizeMessageTimestamp(a.querySelector("[data-message-timestamp]"),h.created_at||h.createdAt),this.clearRevealedOpeningSequenceMessageIds();const u={id:h.id,body:i,attachments:c?[c.src]:[],replied_to:e,product:t,button:s,type:"quick_reply"};bt.eventEmitter.dispatch("webchat:message:sent",u)}async sendTeaserQuickReply(e){e.preventDefault(),e.stopPropagation();const t=e.currentTarget,s=(t.dataset.value||"").trim(),i=[t.dataset.text,t.textContent].map(e=>(e||"").trim()).find(e=>e.length>0),n=s||i;if(!n)return;this.dismissTeaserForSession?.(),this.show();const r=(t.dataset.type||"").trim()||"quick_reply",a=new FormData;a.append("message[body]",n),a.append("session",bt.session),a.append("locale",o.toString()),this.appendOpeningSequenceMessageIds(a);const c=this.buildMessageElement();c.querySelector("[data-body]").innerText=n,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));const l=await this.messagesAPI.create(a);if(l.failed)return clearTimeout(this.optimisticTypingTimeout),this.markMessageFailedFromResponse(l,c);const h=await l.json();c.setAttribute("data-id",h.id),this.localizeMessageTimestamp(c.querySelector("[data-message-timestamp]"),h.created_at||h.createdAt),this.clearRevealedOpeningSequenceMessageIds(),bt.eventEmitter.dispatch("webchat:message:sent",{id:h.id,body:n,attachments:[],type:"quick_reply",teaser:{text:i||n,value:s||n,type:r}}),h.conversation&&h.conversation!==this.conversationIdValue&&(this.conversationIdValue=h.conversation,this.webChatChannel.updateSubscriptionWith(this.conversationIdValue)),this.typingIndicatorVisible&&this.resetTypingIndicatorTimer()}async sendMessage(e){const t={body:this.inputTarget.value,attachments:this.files};if(0===this.inputTarget.value.trim().length&&0===this.files.length)return void(e&&e.target&&e.preventDefault());this.dismissTeaserForSession?.();const s=new FormData;this.inputTarget.value.trim().length>0?s.append("message[body]",this.inputTarget.value):delete t.body,this.files.forEach(e=>{s.append("message[attachments][]",e)}),s.append("session",bt.session),s.append("locale",o.toString()),this.appendOpeningSequenceMessageIds(s);const i=this.buildMessageElement();this.inputTarget.value.trim().length>0?i.querySelector("[data-body]").innerText=this.inputTarget.value:i.querySelector("[data-message-bubble]").remove();const n=this.attachmentContainerTarget.querySelectorAll("img");n.length>0&&n.forEach(e=>{this.messageAttachmentsContainer(i)?.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.focusComposeInput(),this.typingIndicatorVisible||(clearTimeout(this.optimisticTypingTimeout),this.optimisticTypingTimeout=setTimeout(()=>{this.showOptimisticTypingIndicator()},this.optimisticTypingIndicatorWaitValue));const r=await this.messagesAPI.create(s);if(r.failed)return clearTimeout(this.optimisticTypingTimeout),this.markMessageFailedFromResponse(r,i);const a=await r.json();i.setAttribute("data-id",a.id),t.id=a.id,this.localizeMessageTimestamp(i.querySelector("[data-message-timestamp]"),a.created_at||a.createdAt),this.clearRevealedOpeningSequenceMessageIds(),bt.eventEmitter.dispatch("webchat:message:sent",t),a.conversation!==this.conversationIdValue&&(this.conversationIdValue=a.conversation,this.webChatChannel.updateSubscriptionWith(this.conversationIdValue)),this.typingIndicatorVisible&&this.resetTypingIndicatorTimer(),this.attachmentContainerTarget.style.display=""}buildMessageElement(){const e=this.messageTemplateTarget.cloneNode(!0);return e.id=`hellotext--webchat--${this.idValue}--message--${Date.now()}`,e.classList.add("received"),e.style.removeProperty("display"),e.setAttribute("data-controller","hellotext--message"),e.setAttribute("data-hellotext--webchat-target","message"),this.localizeMessageTimestamp(e.querySelector("[data-message-timestamp]"),new Date),e}focusCompose(e){const{target:t}=e,s=["button","a","input","textarea","select","label",'[role="button"]',"em-emoji-picker",'[data-hellotext--webchat--emoji-target~="popover"]','[data-controller~="hellotext--webchat--emoji"]'].join(", ");this.hasInputTarget&&!t.closest(s)&&this.focusComposeInput({moveCursorToEnd:!0})&&e.preventDefault()}closePopoverFromHeader(e){const{target:t}=e;t.closest(".hellotext--webchat-header-channel-button, .hellotext--webchat-close-button")||(e.preventDefault(),this.closePopover())}closePopoverOnEscape(e){"Escape"===e.key&&this.openValue&&(e.preventDefault(),e.stopPropagation(),this.closePopover(),this.triggerTarget?.focus?.())}async markMessageFailedFromResponse(e,t){const s=await this.messageFailureReason(e);this.markMessageFailed(t,s),this.broadcastChannel.postMessage({type:"message:failed",id:t.id,reason:s})}markMessageFailed(e,t){if(!e)return;if(e.classList.add("failed"),!t)return;const s=e.querySelector("[data-message-timestamp]");s&&(s.textContent=t)}localizeMessageTimestamps(e=this.element){e&&(e.matches?.("time[datetime][data-message-timestamp]")?[e]:Array.from(e.querySelectorAll?.("time[datetime][data-message-timestamp]")||[])).forEach(e=>this.localizeMessageTimestamp(e))}localizeMessageTimestamp(e,t=e?.getAttribute("datetime")){if(!e||!t)return;const s=t instanceof Date?t:new Date(t);Number.isNaN(s.getTime())||(e.setAttribute("datetime",s.toISOString()),e.textContent=this.formatMessageTimestamp(s))}formatMessageTimestamp(e){return this.constructor.messageTimestampFormatterFor(o.toString()).format(e)}static messageTimestampFormatterFor(e){const t=e||"default";return this.messageTimestampFormatters[t]||(this.messageTimestampFormatters[t]=this.buildMessageTimestampFormatter(e)),this.messageTimestampFormatters[t]}static buildMessageTimestampFormatter(e){try{return new Intl.DateTimeFormat(e||void 0,ei)}catch(e){return new Intl.DateTimeFormat(void 0,ei)}}async messageFailureReason(e){const t=e?.data||e?.response,s=t?.statusText||"Message failed";try{const e=t?.clone?t.clone():t,s=await(e?.json?.()),i=this.messageFailureReasonFromPayload(s);if(i)return i}catch(e){}try{const e=t?.clone?t.clone():t,i=await(e?.text?.());return this.messageFailureReasonFromText(i)||s}catch(e){return s}}messageFailureReasonFromText(e){if("string"!=typeof e)return null;const t=e.trim();if(!t||t.startsWith("<"))return null;try{return this.messageFailureReasonFromPayload(JSON.parse(t))||t}catch(e){return t}}messageFailureReasonFromPayload(e){return e?[e.error?.message,e.message,e.errors?.message,e.errors?.[0]?.message,e.errors?.[0]?.description].find(e=>"string"==typeof e&&e.trim().length>0):null}messageAttachmentsContainer(e){return e.querySelector("[data-attachments-container], [data-attachment-container]")}incrementUnreadCounter(){this.unreadCounterTarget.style.display="flex";const e=(parseInt(this.unreadCounterTarget.innerText)||0)+1;this.unreadCounterTarget.innerText=Math.min(e,9)}openAttachment(){this.attachmentInputTarget.click()}onFileInputChange(){this.errorMessageContainerTarget.style.display="none";const e=Array.from(this.attachmentInputTarget.files);this.attachmentInputTarget.value="";const t=e.find(e=>{const t=e.type.split("/")[0];return["image","video","audio"].includes(t)?this.mediaValue[t].max_sizethis.createAttachmentElement(e)),this.focusComposeInput()}createAttachmentElement(e){const t=this.attachmentElement();if(this.attachmentContainerTarget.style.display="",t.setAttribute("data-name",e.name),e.type.startsWith("image/")){const s=this.attachmentImageTarget.cloneNode(!0);s.src=URL.createObjectURL(e),s.style.display="block",t.appendChild(s),this.attachmentContainerTarget.appendChild(t),this.attachmentContainerTarget.style.display="flex"}else{const s=t.querySelector("main");s.style.height="5rem",s.style.borderRadius="0.375rem",s.style.backgroundColor="#e5e7eb",s.style.padding="0.25rem",t.querySelector("p[data-attachment-name]").innerText=e.name,this.attachmentContainerTarget.appendChild(t),this.attachmentContainerTarget.style.display="flex"}}removeAttachment({currentTarget:e}){const t=e.closest("[data-hellotext--webchat-target='attachment']");this.files=this.files.filter(e=>e.name!==t.dataset.name),this.attachmentInputTarget.value="",t.remove(),this.focusComposeInput()}attachmentTargetDisconnected(){0===this.attachmentTargets.length&&(this.attachmentContainerTarget.innerHTML="",this.attachmentContainerTarget.style.display="none")}attachmentElement(){const e=this.attachmentTemplateTarget.cloneNode(!0);return e.removeAttribute("hidden"),e.style.display="flex",e.setAttribute("data-hellotext--webchat-target","attachment"),e}onEmojiSelect({detail:e}){const t=this.inputTarget.value,s=this.inputTarget.selectionStart,i=this.inputTarget.selectionEnd;this.inputTarget.value=t.slice(0,s)+e+t.slice(i),this.inputTarget.selectionStart=this.inputTarget.selectionEnd=s+e.length,this.focusComposeInput()}focusComposeInput({moveCursorToEnd:e=!1}={}){if(!this.shouldAutofocusCompose)return!1;if(!1===this.hasInputTarget)return!1;if(void 0===this.hasInputTarget&&!this.inputTarget)return!1;if(this.inputTarget.focus(),e&&"number"==typeof this.inputTarget.selectionStart){const e=this.inputTarget.value.length;this.inputTarget.setSelectionRange(e,e)}return!0}byteToMegabyte(e){return Math.ceil(e/1024/1024)}get middlewares(){return[Vs(this.offsetValue),qs({padding:this.paddingValue}),Us()]}get shouldOpenOnMount(){return"opened"===localStorage.getItem(`hellotext--webchat--${this.idValue}`)&&!this.onMobile}get shouldAutofocusCompose(){return!this.usesVirtualKeyboard}get usesVirtualKeyboard(){if("undefined"==typeof navigator)return!1;const e=navigator.userAgent||"",t="MacIntel"===navigator.platform&&navigator.maxTouchPoints>1,s=ti.test(e),i=!0===navigator.userAgentData?.mobile;return s||t||i||this.hasTouchOnlyPointer}get hasTouchOnlyPointer(){return"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(pointer: coarse)").matches&&window.matchMedia("(hover: none)").matches}get onMobile(){return"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia(`(max-width: ${this.fullScreenThresholdValue}px)`).matches}},ni=i.lg.start();ni.register("hellotext--alert",vt),ni.register("hellotext--form",wt),ni.register("hellotext--webchat",ii),ni.register("hellotext--webchat--emoji",Ws),ni.register("hellotext--message",Tt),window.Hellotext=bt;const ri=bt}};const t={};function s(i){const n=t[i];if(void 0!==n)return n.exports;const r=t[i]={exports:{}};return e[i](r,r.exports,s),r.exports}s.m=e,(()=>{const e=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;let t;s.t=function(i,n){if(1&n&&(i=this(i)),8&n)return i;if("object"==typeof i&&i){if(4&n&&i.__esModule)return i;if(16&n&&"function"==typeof i.then)return i}const r=Object.create(null);s.r(r);const a={};t=t||[null,e({}),e([]),e(e)];for(var o=2&n&&i;("object"==typeof o||"function"==typeof o)&&!~t.indexOf(o);o=e(o))Object.getOwnPropertyNames(o).forEach(e=>a[e]=()=>i[e]);return a.default=()=>i,s.d(r,a),r}})(),s.d=(e,t)=>{if(Array.isArray(t))for(var i=0;iPromise.all(Object.keys(s.f).reduce((t,i)=>(s.f[i](e,t),t),[])),s.u=e=>({160:"webchat-emoji",200:"webchat-emoji-en",437:"webchat-emoji-es"}[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),(()=>{const e={},t="Hellotext:";s.l=(i,n,r,a)=>{if(e[i])return void e[i].push(n);let o,c;if(void 0!==r){const e=document.getElementsByTagName("script");for(var l=0;l{o.onerror=o.onload=null,clearTimeout(u);const n=e[i];if(delete e[i],o.parentNode?.removeChild(o),n?.forEach(e=>e(s)),t)return t(s)},u=setTimeout(h.bind(null,void 0,{type:"timeout",target:o}),12e4);o.onerror=h.bind(null,o.onerror),o.onload=h.bind(null,o.onload),c&&document.head.appendChild(o)}})(),s.r=e=>{Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{let e;s.g.importScripts&&(e=s.g.location+"");const t=s.g.document;if(!e&&t&&("SCRIPT"===t.currentScript?.tagName.toUpperCase()&&(e=t.currentScript.src),!e)){const s=t.getElementsByTagName("script");if(s.length){let t=s.length-1;for(;t>-1&&(!e||!/^http(s?):/.test(e));)e=s[t--].src}}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),s.p=e})(),(()=>{const e={792:0};s.f.j=(t,i)=>{let n=s.o(e,t)?e[t]:void 0;if(0!==n)if(n)i.push(n[2]);else{const r=new Promise((s,i)=>n=e[t]=[s,i]);i.push(n[2]=r);const a=s.p+s.u(t),o=new Error,c=i=>{if(s.o(e,t)&&(n=e[t],0!==n&&(e[t]=void 0),n)){const e=i&&("load"===i.type?"missing":i.type),s=i&&i.target&&i.target.src;o.message="Loading chunk "+t+" failed.\n("+e+": "+s+")",o.name="ChunkLoadError",o.type=e,o.request=s,n[1](o)}};s.l(a,c,"chunk-"+t,t)}};const t=(t,i)=>{let[n,r,a]=i;var o,c,l=0;if(n.some(t=>0!==e[t])){for(o in r)s.o(r,o)&&(s.m[o]=r[o]);a&&a(s)}for(t&&t(i);l(()=>{"use strict";var e={891(e,t,s){s.d(t,{lg:()=>G,xI:()=>ne});class i{constructor(e,t,s){this.eventTarget=e,this.eventName=t,this.eventOptions=s,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 s=e.index,i=t.index;return si?1:0})}}class n{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,s={}){this.application.handleError(e,`Error ${t}`,s)}clearEventListenersForBinding(e){const t=this.fetchEventListenerForBinding(e);t.hasBindings()||(t.disconnect(),this.removeMappedEventListenerFor(e))}removeMappedEventListenerFor(e){const{eventTarget:t,eventName:s,eventOptions:i}=e,n=this.fetchEventListenerMapForEventTarget(t),r=this.cacheKey(s,i);n.delete(r),0==n.size&&this.eventListenerMaps.delete(t)}fetchEventListenerForBinding(e){const{eventTarget:t,eventName:s,eventOptions:i}=e;return this.fetchEventListener(t,s,i)}fetchEventListener(e,t,s){const i=this.fetchEventListenerMapForEventTarget(e),n=this.cacheKey(t,s);let r=i.get(n);return r||(r=this.createEventListener(e,t,s),i.set(n,r)),r}createEventListener(e,t,s){const n=new i(e,t,s);return this.started&&n.connect(),n}fetchEventListenerMapForEventTarget(e){let t=this.eventListenerMaps.get(e);return t||(t=new Map,this.eventListenerMaps.set(e,t)),t}cacheKey(e,t){const s=[e];return Object.keys(t).sort().forEach(e=>{s.push(`${t[e]?"":"!"}${e}`)}),s.join(":")}}const r={stop:({event:e,value:t})=>(t&&e.stopPropagation(),!0),prevent:({event:e,value:t})=>(t&&e.preventDefault(),!0),self:({event:e,value:t,element:s})=>!t||s===e.target},o=/^(?:(?:([^.]+?)\+)?(.+?)(?:\.(.+?))?(?:@(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 h(e){return e.replace(/([A-Z])/g,(e,t)=>`-${t.toLowerCase()}`)}function u(e){return null!=e}function d(e,t){return Object.prototype.hasOwnProperty.call(e,t)}const p=["meta","ctrl","alt","shift"];class m{constructor(e,t,s,i){this.element=e,this.index=t,this.eventTarget=s.eventTarget||e,this.eventName=s.eventName||function(e){const t=e.tagName.toLowerCase();if(t in g)return g[t](e)}(e)||f("missing event name"),this.eventOptions=s.eventOptions||{},this.identifier=s.identifier||f("missing identifier"),this.methodName=s.methodName||f("missing method name"),this.keyFilter=s.keyFilter||"",this.schema=i}static forToken(e,t){return new this(e.element,e.index,function(e){const t=e.trim().match(o)||[];let s=t[2],i=t[3];return i&&!["keydown","keyup","keypress"].includes(s)&&(s+=`.${i}`,i=""),{eventTarget:(n=t[4],"window"==n?window:"document"==n?document:void 0),eventName:s,eventOptions:t[7]?(r=t[7],r.split(":").reduce((e,t)=>Object.assign(e,{[t.replace(/^!/,"")]:!/^!/.test(t)}),{})):{},identifier:t[5],methodName:t[6],keyFilter:t[1]||i};var n,r}(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 s=t.filter(e=>!p.includes(e))[0];return!!s&&(d(this.keyMappings,s)||f(`contains unknown key filter: ${this.keyFilter}`),this.keyMappings[s].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:s,value:i}of Array.from(this.element.attributes)){const n=s.match(t),r=n&&n[1];r&&(e[a(r)]=b(i))}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[s,i,n,r]=p.map(e=>t.includes(e));return e.metaKey!==s||e.ctrlKey!==i||e.altKey!==n||e.shiftKey!==r}}const g={a:()=>"click",button:()=>"click",form:()=>"submit",details:()=>"toggle",input:e=>"submit"==e.getAttribute("type")?"click":"input",select:()=>"change",textarea:()=>"input"};function f(e){throw new Error(e)}function b(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:s}=this.context.application,{controller:i}=this.context;let n=!0;for(const[r,o]of Object.entries(this.eventOptions))if(r in s){const a=s[r];n=n&&a({name:r,value:o,event:e,element:t,controller:i})}return n}prepareActionEvent(e){return Object.assign(e,{params:this.action.params})}invokeWithEvent(e){const{target:t,currentTarget:s}=e;try{this.method.call(this.controller,e),this.context.logDebugActivity(this.methodName,{event:e,target:t,currentTarget:s,action:this.methodName})}catch(t){const{identifier:s,controller:i,element:n,index:r}=this,o={identifier:s,controller:i,element:n,index:r,event:e};this.context.handleError(t,`invoking action "${this.action}"`,o)}}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 v{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 s of this.matchElementsInTree(e))t.call(this,s)}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,s){this.attributeName=t,this.delegate=s,this.elementObserver=new v(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]:[],s=Array.from(e.querySelectorAll(this.selector));return t.concat(s)}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 T(e,t){let s=e.get(t);return s||(s=new Set,e.set(t,s)),s}class S{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,s){T(e,t).add(s)}(this.valuesByKey,e,t)}delete(e,t){!function(e,t,s){T(e,t).delete(s),function(e,t){const s=e.get(t);null!=s&&0==s.size&&e.delete(t)}(e,t)}(this.valuesByKey,e,t)}has(e,t){const s=this.valuesByKey.get(e);return null!=s&&s.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,s])=>s.has(e)).map(([e,t])=>e)}}class C{constructor(e,t,s,i){this._selector=t,this.details=i,this.elementObserver=new v(e,this),this.delegate=s,this.matchesByElement=new S}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 s=e.matches(t);return this.delegate.selectorMatchElement?s&&this.delegate.selectorMatchElement(e,this.details):s}return!1}matchElementsInTree(e){const{selector:t}=this;if(t){const s=this.matchElement(e)?[e]:[],i=Array.from(e.querySelectorAll(t)).filter(e=>this.matchElement(e));return s.concat(i)}return[]}elementMatched(e){const{selector:t}=this;t&&this.selectorMatched(e,t)}elementUnmatched(e){const t=this.matchesByElement.getKeysForValue(e);for(const s of t)this.selectorUnmatched(e,s)}elementAttributeChanged(e,t){const{selector:s}=this;if(s){const t=this.matchElement(e),i=this.matchesByElement.has(s,e);t&&!i?this.selectorMatched(e,s):!t&&i&&this.selectorUnmatched(e,s)}}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 O{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 s=this.delegate.getStringMapKeyForAttribute(e);if(null!=s){this.stringMap.has(e)||this.stringMapKeyAdded(s,e);const i=this.element.getAttribute(e);if(this.stringMap.get(e)!=i&&this.stringMapValueChanged(i,s,t),null==i){const t=this.stringMap.get(e);this.stringMap.delete(e),t&&this.stringMapKeyRemoved(s,e,t)}else this.stringMap.set(e,i)}}stringMapKeyAdded(e,t){this.delegate.stringMapKeyAdded&&this.delegate.stringMapKeyAdded(e,t)}stringMapValueChanged(e,t,s){this.delegate.stringMapValueChanged&&this.delegate.stringMapValueChanged(e,t,s)}stringMapKeyRemoved(e,t,s){this.delegate.stringMapKeyRemoved&&this.delegate.stringMapKeyRemoved(e,t,s)}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,s){this.attributeObserver=new w(e,t,this),this.delegate=s,this.tokensByElement=new S}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,s]=this.refreshTokensForElement(e);this.tokensUnmatched(t),this.tokensMatched(s)}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),s=this.readTokensForElement(e),i=function(e,t){const s=Math.max(e.length,t.length);return Array.from({length:s},(s,i)=>[e[i],t[i]])}(t,s).findIndex(([e,t])=>{return i=t,!((s=e)&&i&&s.index==i.index&&s.content==i.content);var s,i});return-1==i?[[],[]]:[t.slice(i),s.slice(i)]}readTokensForElement(e){const t=this.attributeName;return function(e,t,s){return e.trim().split(/\s+/).filter(e=>e.length).map((e,i)=>({element:t,attributeName:s,content:e,index:i}))}(e.getAttribute(t)||"",e,t)}}class A{constructor(e,t,s){this.tokenListObserver=new E(e,t,this),this.delegate=s,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:s}=this.fetchParseResultForToken(e);s&&(this.fetchValuesByTokenForElement(t).set(e,s),this.delegate.elementMatchedValue(t,s))}tokenUnmatched(e){const{element:t}=e,{value:s}=this.fetchParseResultForToken(e);s&&(this.fetchValuesByTokenForElement(t).delete(e),this.delegate.elementUnmatchedValue(t,s))}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 x{constructor(e,t){this.context=e,this.delegate=t,this.bindingsByAction=new Map}start(){this.valueListObserver||(this.valueListObserver=new A(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=m.forToken(e,this.schema);if(t.identifier==this.identifier)return t}elementMatchedValue(e,t){this.connectAction(t)}elementUnmatchedValue(e,t){this.disconnectAction(t)}}class M{constructor(e,t){this.context=e,this.receiver=t,this.stringMapObserver=new O(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 s=this.valueDescriptorMap[t];this.hasValue(e)||this.invokeChangedCallback(e,s.writer(this.receiver[e]),s.writer(s.defaultValue))}stringMapValueChanged(e,t,s){const i=this.valueDescriptorNameMap[t];null!==e&&(null===s&&(s=i.writer(i.defaultValue)),this.invokeChangedCallback(t,e,s))}stringMapKeyRemoved(e,t,s){const i=this.valueDescriptorNameMap[e];this.hasValue(e)?this.invokeChangedCallback(e,i.writer(this.receiver[e]),s):this.invokeChangedCallback(e,i.writer(i.defaultValue),s)}invokeChangedCallbacksForDefaultValues(){for(const{key:e,name:t,defaultValue:s,writer:i}of this.valueDescriptors)null==s||this.controller.data.has(e)||this.invokeChangedCallback(t,i(s),void 0)}invokeChangedCallback(e,t,s){const i=`${e}Changed`,n=this.receiver[i];if("function"==typeof n){const i=this.valueDescriptorNameMap[e];try{const e=i.reader(t);let r=s;s&&(r=i.reader(s)),n.call(this.receiver,e,r)}catch(e){throw e instanceof TypeError&&(e.message=`Stimulus Value "${this.context.identifier}.${i.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 s=this.valueDescriptorMap[t];e[s.name]=s}),e}hasValue(e){const t=`has${l(this.valueDescriptorNameMap[e].name)}`;return this.receiver[t]}}class k{constructor(e,t){this.context=e,this.delegate=t,this.targetsByName=new S}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 s;this.targetsByName.has(t,e)||(this.targetsByName.add(t,e),null===(s=this.tokenListObserver)||void 0===s||s.pause(()=>this.delegate.targetConnected(e,t)))}disconnectTarget(e,t){var s;this.targetsByName.has(t,e)&&(this.targetsByName.delete(t,e),null===(s=this.tokenListObserver)||void 0===s||s.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 I(e,t){const s=L(e);return Array.from(s.reduce((e,s)=>(function(e,t){const s=e[t];return Array.isArray(s)?s:[]}(s,t).forEach(t=>e.add(t)),e),new Set))}function L(e){const t=[];for(;e;)t.push(e),e=Object.getPrototypeOf(e);return t.reverse()}class P{constructor(e,t){this.started=!1,this.context=e,this.delegate=t,this.outletsByName=new S,this.outletElementsByName=new S,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:s}){const i=this.getOutlet(e,s);i&&this.connectOutlet(i,e,s)}selectorUnmatched(e,t,{outletName:s}){const i=this.getOutletFromMap(e,s);i&&this.disconnectOutlet(i,e,s)}selectorMatchElement(e,{outletName:t}){const s=this.selector(t),i=this.hasOutlet(e,t),n=e.matches(`[${this.schema.controllerAttribute}~=${t}]`);return!!s&&i&&n&&e.matches(s)}elementMatchedAttribute(e,t){const s=this.getOutletNameFromOutletAttributeName(t);s&&this.updateSelectorObserverForOutlet(s)}elementAttributeValueChanged(e,t){const s=this.getOutletNameFromOutletAttributeName(t);s&&this.updateSelectorObserverForOutlet(s)}elementUnmatchedAttribute(e,t){const s=this.getOutletNameFromOutletAttributeName(t);s&&this.updateSelectorObserverForOutlet(s)}connectOutlet(e,t,s){var i;this.outletElementsByName.has(s,t)||(this.outletsByName.add(s,e),this.outletElementsByName.add(s,t),null===(i=this.selectorObserverMap.get(s))||void 0===i||i.pause(()=>this.delegate.outletConnected(e,t,s)))}disconnectOutlet(e,t,s){var i;this.outletElementsByName.has(s,t)&&(this.outletsByName.delete(s,e),this.outletElementsByName.delete(s,t),null===(i=this.selectorObserverMap.get(s))||void 0===i||i.pause(()=>this.delegate.outletDisconnected(e,t,s)))}disconnectAllOutlets(){for(const e of this.outletElementsByName.keys)for(const t of this.outletElementsByName.getValuesForKey(e))for(const s of this.outletsByName.getValuesForKey(e))this.disconnectOutlet(s,t,e)}updateSelectorObserverForOutlet(e){const t=this.selectorObserverMap.get(e);t&&(t.selector=this.selector(e))}setupSelectorObserverForOutlet(e){const t=this.selector(e),s=new C(document.body,t,this,{outletName:e});this.selectorObserverMap.set(e,s),s.start()}setupAttributeObserverForOutlet(e){const t=this.attributeNameForOutletName(e),s=new w(this.scope.element,t,this);this.attributeObserverMap.set(e,s),s.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 S;return this.router.modules.forEach(t=>{I(t.definition.controllerConstructor,"outlets").forEach(s=>e.add(s,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 _{constructor(e,t){this.logDebugActivity=(e,t={})=>{const{identifier:s,controller:i,element:n}=this;t=Object.assign({identifier:s,controller:i,element:n},t),this.application.logDebugActivity(this.identifier,e,t)},this.module=e,this.scope=t,this.controller=new e.controllerConstructor(this),this.bindingObserver=new x(this,this.dispatcher),this.valueObserver=new M(this,this.controller),this.targetObserver=new k(this,this),this.outletObserver=new P(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,s={}){const{identifier:i,controller:n,element:r}=this;s=Object.assign({identifier:i,controller:n,element:r},s),this.application.handleError(e,`Error ${t}`,s)}targetConnected(e,t){this.invokeControllerMethod(`${t}TargetConnected`,e)}targetDisconnected(e,t){this.invokeControllerMethod(`${t}TargetDisconnected`,e)}outletConnected(e,t,s){this.invokeControllerMethod(`${c(s)}OutletConnected`,e,t)}outletDisconnected(e,t,s){this.invokeControllerMethod(`${c(s)}OutletDisconnected`,e,t)}invokeControllerMethod(e,...t){const s=this.controller;"function"==typeof s[e]&&s[e](...t)}}const N="function"==typeof Object.getOwnPropertySymbols?e=>[...Object.getOwnPropertyNames(e),...Object.getOwnPropertySymbols(e)]:Object.getOwnPropertyNames,D=(()=>{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 F{constructor(e,t){this.application=e,this.definition=function(e){return{identifier:e.identifier,controllerConstructor:(t=e.controllerConstructor,function(e,t){const s=D(e),i=function(e,t){return N(t).reduce((s,i)=>{const n=function(e,t,s){const i=Object.getOwnPropertyDescriptor(e,s);if(!i||!("value"in i)){const e=Object.getOwnPropertyDescriptor(t,s).value;return i&&(e.get=i.get||e.get,e.set=i.set||e.set),e}}(e,t,i);return n&&Object.assign(s,{[i]:n}),s},{})}(e.prototype,t);return Object.defineProperties(s.prototype,i),s}(t,function(e){return I(e,"blessings").reduce((t,s)=>{const i=s(e);for(const e in i){const s=t[e]||{};t[e]=Object.assign(s,i[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 _(this,e),this.contextsByScope.set(e,t)),t}}class R{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 B{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 s=this.getAttributeNameForKey(e);return this.element.setAttribute(s,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}-${h(e)}`}}class ${constructor(e){this.warnedKeysByObject=new WeakMap,this.logger=e}warn(e,t,s){let i=this.warnedKeysByObject.get(e);i||(i=new Set,this.warnedKeysByObject.set(e,i)),i.has(t)||(i.add(t),this.logger.warn(s,e))}}function j(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 j(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 j(this.schema.targetAttribute,t)}deprecate(e,t){if(e){const{identifier:s}=this,i=this.schema.targetAttribute,n=this.schema.targetAttributeForScope(s);this.guide.warn(e,`target:${t}`,`Please replace ${i}="${s}.${t}" with ${n}="${t}". The ${i} attribute is deprecated and will be removed in a future version of Stimulus.`)}return e}get guide(){return this.scope.guide}}class q{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(s=>this.matchesElement(s,e,t))[0]}findAllElements(e,t){return this.scope.queryElements(e).filter(s=>this.matchesElement(s,e,t))}matchesElement(e,t,s){const i=e.getAttribute(this.scope.schema.controllerAttribute)||"";return e.matches(t)&&i.split(" ").includes(s)}}class U{constructor(e,t,s,i){this.targets=new V(this),this.classes=new R(this),this.data=new B(this),this.containsElement=e=>e.closest(this.controllerSelector)===this.element,this.schema=e,this.element=t,this.identifier=s,this.guide=new $(i),this.outlets=new q(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 j(this.schema.controllerAttribute,this.identifier)}get isDocumentScope(){return this.element===document.documentElement}get documentScope(){return this.isDocumentScope?this:new U(this.schema,document.documentElement,this.identifier,this.guide.logger)}}class z{constructor(e,t,s){this.element=e,this.schema=t,this.delegate=s,this.valueListObserver=new A(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:s}=e;return this.parseValueForElementAndIdentifier(t,s)}parseValueForElementAndIdentifier(e,t){const s=this.fetchScopesByIdentifierForElement(e);let i=s.get(t);return i||(i=this.delegate.createScopeForElementAndIdentifier(e,t),s.set(t,i)),i}elementMatchedValue(e,t){const s=(this.scopeReferenceCounts.get(t)||0)+1;this.scopeReferenceCounts.set(t,s),1==s&&this.delegate.scopeConnected(t)}elementUnmatchedValue(e,t){const s=this.scopeReferenceCounts.get(t);s&&(this.scopeReferenceCounts.set(t,s-1),1==s&&this.delegate.scopeDisconnected(t))}fetchScopesByIdentifierForElement(e){let t=this.scopesByIdentifierByElement.get(e);return t||(t=new Map,this.scopesByIdentifierByElement.set(e,t)),t}}class W{constructor(e){this.application=e,this.scopeObserver=new z(this.element,this.schema,this),this.scopesByIdentifier=new S,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 F(this.application,e);this.connectModule(t);const s=e.controllerConstructor.afterLoad;s&&s.call(e.controllerConstructor,e.identifier,this.application)}unloadIdentifier(e){const t=this.modulesByIdentifier.get(e);t&&this.disconnectModule(t)}getContextForElementAndIdentifier(e,t){const s=this.modulesByIdentifier.get(t);if(s)return s.contexts.find(t=>t.element==e)}proposeToConnectScopeForElementAndIdentifier(e,t){const s=this.scopeObserver.parseValueForElementAndIdentifier(e,t);s?this.scopeObserver.elementMatchedValue(s.element,s):console.error(`Couldn't find or create scope for identifier: "${t}" and element:`,e)}handleError(e,t,s){this.application.handleError(e,t,s)}createScopeForElementAndIdentifier(e,t){return new U(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 H={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"},K("abcdefghijklmnopqrstuvwxyz".split("").map(e=>[e,e]))),K("0123456789".split("").map(e=>[e,e])))};function K(e){return e.reduce((e,[t,s])=>Object.assign(Object.assign({},e),{[t]:s}),{})}class G{constructor(e=document.documentElement,t=H){this.logger=console,this.debug=!1,this.logDebugActivity=(e,t,s={})=>{this.debug&&this.logFormattedMessage(e,t,s)},this.element=e,this.schema=t,this.dispatcher=new n(this),this.router=new W(this),this.actionDescriptorFilters=Object.assign({},r)}static start(e,t){const s=new this(e,t);return s.start(),s}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 s=this.router.getContextForElementAndIdentifier(e,t);return s?s.controller:null}handleError(e,t,s){var i;this.logger.error("%s\n\n%o\n\n%o",t,e,s),null===(i=window.onerror)||void 0===i||i.call(window,t,"",0,0,e)}logFormattedMessage(e,t,s={}){s=Object.assign({application:this},s),this.logger.groupCollapsed(`${e} #${t}`),this.logger.log("details:",Object.assign({},s)),this.logger.groupEnd()}}function J(e,t,s){return e.application.getControllerForElementAndIdentifier(t,s)}function Y(e,t,s){let i=J(e,t,s);return i||(e.application.router.proposeToConnectScopeForElementAndIdentifier(t,s),i=J(e,t,s),i||void 0)}function Z([e,t],s){return function(e){const{token:t,typeDefinition:s}=e,i=`${h(t)}-value`,n=function(e){const{controller:t,token:s,typeDefinition:i}=e,n=function(e){const{controller:t,token:s,typeObject:i}=e,n=u(i.type),r=u(i.default),o=n&&r,a=n&&!r,c=!n&&r,l=X(i.type),h=Q(e.typeObject.default);if(a)return l;if(c)return h;if(l!==h)throw new Error(`The specified default value for the Stimulus Value "${t?`${t}.${s}`:s}" must match the defined type "${l}". The provided default value of "${i.default}" is of type "${h}".`);return o?l:void 0}({controller:t,token:s,typeObject:i}),r=Q(i),o=X(i),a=n||r||o;if(a)return a;throw new Error(`Unknown value type "${t?`${t}.${i}`:s}" for "${s}" value`)}(e);return{type:n,key:i,name:a(i),get defaultValue(){return function(e){const t=X(e);if(t)return ee[t];const s=d(e,"default"),i=d(e,"type"),n=e;if(s)return n.default;if(i){const{type:e}=n,t=X(e);if(t)return ee[t]}return e}(s)},get hasCustomDefaultValue(){return void 0!==Q(s)},reader:te[n],writer:se[n]||se.default}}({controller:s,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 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},se={default:function(e){return`${e}`},array:ie,object:ie};function ie(e){return JSON.stringify(e)}class ne{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:s={},prefix:i=this.identifier,bubbles:n=!0,cancelable:r=!0}={}){const o=new CustomEvent(i?`${i}:${e}`:e,{detail:s,bubbles:n,cancelable:r});return t.dispatchEvent(o),o}}ne.blessings=[function(e){return I(e,"classes").reduce((e,t)=>{return Object.assign(e,(s=t,{[`${s}Class`]:{get(){const{classes:e}=this;if(e.has(s))return e.get(s);{const t=e.getAttributeName(s);throw new Error(`Missing attribute "${t}"`)}}},[`${s}Classes`]:{get(){return this.classes.getAll(s)}},[`has${l(s)}Class`]:{get(){return this.classes.has(s)}}}));var s},{})},function(e){return I(e,"targets").reduce((e,t)=>{return Object.assign(e,(s=t,{[`${s}Target`]:{get(){const e=this.targets.find(s);if(e)return e;throw new Error(`Missing target element "${s}" for "${this.identifier}" controller`)}},[`${s}Targets`]:{get(){return this.targets.findAll(s)}},[`has${l(s)}Target`]:{get(){return this.targets.has(s)}}}));var s},{})},function(e){const t=function(e,t){return L(e).reduce((e,s)=>(e.push(...function(e,t){const s=e[t];return s?Object.keys(s).map(e=>[e,s[e]]):[]}(s,t)),e),[])}(e,"values"),s={valueDescriptorMap:{get(){return t.reduce((e,t)=>{const s=Z(t,this.identifier),i=this.data.getAttributeNameForKey(s.key);return Object.assign(e,{[i]:s})},{})}}};return t.reduce((e,t)=>Object.assign(e,function(e){const t=Z(e,void 0),{key:s,name:i,reader:n,writer:r}=t;return{[i]:{get(){const e=this.data.get(s);return null!==e?n(e):t.defaultValue},set(e){void 0===e?this.data.delete(s):this.data.set(s,r(e))}},[`has${l(i)}`]:{get(){return this.data.has(s)||t.hasCustomDefaultValue}}}}(t)),s)},function(e){return I(e,"outlets").reduce((e,t)=>Object.assign(e,function(e){const t=c(e);return{[`${t}Outlet`]:{get(){const t=this.outlets.find(e),s=this.outlets.getSelectorForOutletName(e);if(t){const s=Y(this,t,e);if(s)return s;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 "${s}".`)}},[`${t}Outlets`]:{get(){const t=this.outlets.findAll(e);return t.length>0?t.map(t=>{const s=Y(this,t,e);if(s)return s;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),s=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 "${s}".`)}},[`${t}OutletElements`]:{get(){return this.outlets.findAll(e)}},[`has${l(t)}Outlet`]:{get(){return this.outlets.has(e)}}}}(t)),{})}],ne.targets=[],ne.outlets=[],ne.values={}},778(e,t,s){s.d(t,{default:()=>li});var i=s(891);class n extends Error{constructor(e){super(`${e} is not valid. Please provide a valid event name`),this.name="InvalidEvent"}}class r{static events=["session-set","utm-set","forms:collected","form:completed","popup:mounted","popup:opened","popup:closed","alert:shown","alert:dismissed","alert:accepted","webchat:mounted","webchat:opened","webchat:closed","webchat:message:sent","webchat:message:received","cart.added"];static valid(e){return r.exists(e)}static invalid(e){return!this.valid(e)}static exists(e){return void 0!==this.events.find(t=>t===e)}constructor(){this.subscribers={}}addSubscriber(e,t){if(r.invalid(e))throw new n(e);this.subscribers={...this.subscribers,[e]:this.subscribers[e]?[...this.subscribers[e],t]:[t]}}removeSubscriber(e,t){if(r.invalid(e))throw new n(e);this.subscribers[e]&&(this.subscribers[e]=this.subscribers[e].filter(e=>e!==t))}dispatch(e,t){this.subscribers[e]?.forEach(e=>{e(t)})}get listeners(){return 0!==Object.keys(this.subscribers).length}}class o{static autoMount=!0;static successMessage=!0;static assign(e){return e&&Object.entries(e).forEach(([e,t])=>{this[e]=t}),this}static get shouldShowSuccessMessage(){return this.successMessage}}class a{static _identifier;static set identifier(e){this._identifier=e}static get identifier(){return this._identifier?this._identifier:this.#e||this.#t||this.#s||"en"}static toString(){return this.identifier}static get#e(){return document?.documentElement?.lang}static get#t(){return document?.querySelector('meta[name="locale"]')?.content}static get#s(){return navigator?.language?.split("-")[0]}}class c{static _id;static _container="body";static _device="auto";static set id(e){this._id=e}static get id(){return this._id}static set container(e){this._container=e}static get container(){return this._container}static set device(e){if(!["auto","mobile","desktop"].includes(e))throw new Error(`Invalid popup device value: ${e}`);this._device=e}static get device(){return this._device}static assign(e){return e&&Object.entries(e).forEach(([e,t])=>{this[e]=t}),this}}const l={TOP_LEFT:"top-left",TOP_RIGHT:"top-right",BOTTOM_LEFT:"bottom-left",BOTTOM_RIGHT:"bottom-right"},h={ABSOLUTE:"absolute",FIXED:"fixed"},u={MODAL:"modal",POPOVER:"popover"};class d{static _id;static _container="body";static _placement="bottom-right";static _style={};static _appearance={};static _whatsapp={};static _mode=u.POPOVER;static _behaviour=null;static _hasBehaviourOverride=!1;static _strategy=null;static set container(e){this._container=e}static get container(){return this._container}static set placement(e){if(!Object.values(l).includes(e))throw new Error(`Invalid placement value: ${e}`);this._placement=e}static get placement(){return this._placement}static set id(e){this._id=e}static get id(){return this._id}static get isSet(){return!!this._id}static get style(){return this._style}static set style(e){if("object"!=typeof e)throw new Error("Style must be an object");Object.entries(e).forEach(([e,t])=>{if(!["primaryColor","secondaryColor","typography"].includes(e))throw new Error(`Invalid style property: ${e}`);if("typography"!==e&&!this.isHexOrRgba(t))throw new Error(`Invalid color value: ${t} for ${e}. Colors must be hex or rgb/a.`)}),this._style=e}static get appearance(){return this._appearance}static set appearance(e){if(!this.isPlainObject(e))throw new Error("Appearance must be an object");Object.entries(e).forEach(([e,t])=>{if(!["header","launcher"].includes(e))throw new Error(`Invalid appearance property: ${e}`);if(!this.isPlainObject(t))throw new Error(`Appearance ${e} must be an object`);Object.entries(t).forEach(([t,s])=>{if("header"===e&&"name"!==t)throw new Error(`Invalid appearance header property: ${t}`);if("launcher"===e&&"iconUrl"!==t)throw new Error(`Invalid appearance launcher property: ${t}`);if(null!=s&&"string"!=typeof s)throw new Error(`Invalid appearance ${e}.${t} value: ${s}`)})}),this._appearance=e}static get whatsapp(){return this._whatsapp}static set whatsapp(e){if(!this.isPlainObject(e))throw new Error("WhatsApp must be an object");Object.entries(e).forEach(([e,t])=>{if(!["number","restrictToChannel"].includes(e))throw new Error(`Invalid WhatsApp property: ${e}`);if(null!=t){if("number"===e&&"string"!=typeof t)throw new Error(`Invalid WhatsApp number value: ${t}`);if("restrictToChannel"===e&&"boolean"!=typeof t)throw new Error(`Invalid WhatsApp restrictToChannel value: ${t}`)}}),this._whatsapp=e}static get mode(){return this._mode}static set mode(e){if(!Object.values(u).includes(e))throw new Error(`Invalid mode value: ${e}`);this._mode=e}static get behaviour(){return this._behaviour}static set behaviour(e){if(null!=e){if("object"!=typeof e||Array.isArray(e))throw new Error(`Invalid behaviour value: ${e}`);this._behaviour=e}else this._behaviour=e}static get hasBehaviourOverride(){return this._hasBehaviourOverride}static set behaviourOverride(e){this._hasBehaviourOverride=!!e}static get strategy(){return this._strategy?this._strategy:"body"==this.container?h.FIXED:h.ABSOLUTE}static set strategy(e){if(e&&!Object.values(h).includes(e))throw new Error(`Invalid strategy value: ${e}`);this._strategy=e}static assign(e){return e&&Object.entries(e).forEach(([e,t])=>{this[e]=t}),this}static isHexOrRgba(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)}static isPlainObject(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}}const p={TOP_LEFT:"top-left",TOP_RIGHT:"top-right",BOTTOM_LEFT:"bottom-left",BOTTOM_RIGHT:"bottom-right"};class m{static _id;static _container="body";static _placement="bottom-right";static _appearance={};static _number=null;static _body=null;static set id(e){this._id=e}static get id(){return this._id}static set container(e){this._container=e}static get container(){return this._container}static set placement(e){if(!Object.values(p).includes(e))throw new Error(`Invalid placement value: ${e}`);this._placement=e}static get placement(){return this._placement}static get appearance(){return this._appearance}static set appearance(e){if(!this.isPlainObject(e))throw new Error("Appearance must be an object");Object.entries(e).forEach(([e,t])=>{if("launcher"!==e)throw new Error(`Invalid appearance property: ${e}`);if(!this.isPlainObject(t))throw new Error(`Appearance ${e} must be an object`);Object.entries(t).forEach(([t,s])=>{if("iconUrl"!==t)throw new Error(`Invalid appearance launcher property: ${t}`);if(null!=s&&"string"!=typeof s)throw new Error(`Invalid appearance ${e}.${t} value: ${s}`)})}),this._appearance=e}static get number(){return this._number}static set number(e){if(null!=e&&"string"!=typeof e)throw new Error(`Invalid number value: ${e}`);this._number=e}static get body(){return this._body}static set body(e){if(null!=e&&"string"!=typeof e)throw new Error(`Invalid body value: ${e}`);this._body=e}static assign(e){return e&&Object.entries(e).forEach(([e,t])=>{this[e]=t}),this}static isPlainObject(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}}class g{static serviceWorkerUrl=null;static channelId=null;static assign(e){return this.serviceWorkerUrl=e?.serviceWorkerUrl||null,this.channelId=e?.channelId||null,this}}class f{static apiRoot="https://api.hellotext.com/v1";static actionCableUrl="wss://www.hellotext.com/cable";static autoGenerateSession=!0;static session=null;static forms=o;static popup=c;static webchat=d;static whatsapp=m;static push=g;static assign(e){if(e){const t=Object.prototype.hasOwnProperty.call(e,"apiRoot")&&!Object.prototype.hasOwnProperty.call(e,"actionCableUrl");Object.entries(e).forEach(([e,t])=>{"forms"===e?this.forms=o.assign(t):"popup"===e?this.popup=c.assign(t):"webchat"===e?this.webchat=d.assign(t):"whatsappWidget"===e?this.whatsapp=m.assign(t):"push"===e?this.push=g.assign(t):this[e]=t}),t&&(this.actionCableUrl=this.actionCableUrlForApiRoot(this.apiRoot))}return this}static set locale(e){a.identifier=e}static get locale(){return a.toString()}static endpoint(e){return`${this.apiRoot}/${e}`}static actionCableUrlForApiRoot(e){try{const t=new URL(e),s="https:"===t.protocol?"wss:":"ws:";return t.protocol=s,t.pathname="/cable",t.search="",t.hash="",t.toString()}catch(e){return this.actionCableUrl}}}const b=class{static get endpoint(){return f.endpoint("public/businesses")}static async get(e){return fetch(`${this.endpoint}/${e}`,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json","Content-Type":"application/json"}})}};class y{static get inPreviewMode(){return(new this).inPreviewMode}constructor(){this.urlSearchParams=new URLSearchParams(window.location.search)}get(e){return this.urlSearchParams.get(this.toHellotextParam(e))}has(e){return this.urlSearchParams.has(this.toHellotextParam(e))}get inPreviewMode(){return this.has("preview")}get session(){return this.get("session")}toHellotextParam(e){return`hello_${e}`}}class v{#i;constructor(e,t){this.response=t,this.#i=e}get data(){return this.response}async json(){return await this.response.json()}get failed(){return!1===this.#i}get succeeded(){return!0===this.#i}}class w{static get endpoint(){return f.endpoint("track/events")}static async create({headers:e,body:t,keepalive:s=!1}){if(y.inPreviewMode)return new v(!0,{received:!0});const i={method:"POST",headers:e,body:JSON.stringify(t)};s&&(i.keepalive=!0);const n=await fetch(this.endpoint,i);return new v(200===n.status,await n.json())}}class T{static get endpoint(){return f.endpoint("public/forms")}static async get(e){const t=new URL(`${this.endpoint}/${e}`);return t.searchParams.append("session",wt.session),t.searchParams.append("locale",a.toString()),fetch(t,{method:"GET",headers:wt.headers})}static async submit(e,t){const s=await fetch(`${this.endpoint}/${e}/submissions`,{method:"POST",headers:wt.headers,body:JSON.stringify({session:wt.session,...t})});return new v(s.ok,s)}}const S=class{static get endpoint(){return f.endpoint("public/identifications")}static async create(e={}){const t=await fetch(this.endpoint,{method:"POST",headers:wt.headers,body:JSON.stringify({session:wt.session,...e})});return new v(t.ok,t)}},C=class{static get endpoint(){return f.endpoint("public/popups")}static async get(e){const t=new URL(`${this.endpoint}/${e}`);t.searchParams.append("session",wt.session),t.searchParams.append("locale",a.toString()),t.searchParams.append("device",this.runtimeDevice);const s=await this.fetchPopup(t);if(!s.ok)return null;const i=await this.parsePopupResponse(s);return i?(wt.business.data||(wt.business.setData(i.business),wt.business.setLocale(i.locale)),(new DOMParser).parseFromString(i.html,"text/html").querySelector("article")):null}static async submit(e,t,s=this.idempotencyKey()){const i=await fetch(`${this.endpoint}/${e}/submissions`,{method:"POST",headers:{...wt.headers,"Idempotency-Key":s},body:JSON.stringify({session:wt.session,popup_submission:t})});return new v(i.ok,i)}static async resend(e,t,s){const i=await fetch(`${this.endpoint}/${e}/submissions/${t}/resend`,{method:"POST",headers:wt.headers,body:JSON.stringify({token:s})});return new v(i.ok,i)}static async cancel(e,t,s){const i=await fetch(`${this.endpoint}/${e}/submissions/${t}/cancel`,{method:"POST",headers:wt.headers,body:JSON.stringify({token:s})});return new v(i.ok,i)}static idempotencyKey(){return"undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID?crypto.randomUUID():`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}static async fetchPopup(e){try{return await fetch(e,{method:"GET",headers:wt.headers})}catch(e){return{ok:!1}}}static get runtimeDevice(){return"auto"!==f.popup.device?f.popup.device:window.innerWidth<=767?"mobile":"desktop"}static async parsePopupResponse(e){try{return await e.json()}catch(e){return null}}},O=class{static get endpoint(){return f.endpoint("public/webchats")}static async get(e){const t=new URL(`${this.endpoint}/${e}`);t.searchParams.append("session",wt.session),t.searchParams.append("locale",a.toString()),Object.entries(f.webchat.style).forEach(([e,s])=>{t.searchParams.append(`style[${e}]`,s)}),this.appendWebchatOverrides(t),t.searchParams.append("placement",f.webchat.placement);const s=await fetch(t,{method:"GET",headers:wt.headers}),i=await s.json();return wt.business.data||(wt.business.setData(i.business),wt.business.setLocale(i.locale)),(new DOMParser).parseFromString(i.html,"text/html").querySelector("article")}static appendWebchatOverrides(e){const{appearance:t,whatsapp:s}=f.webchat;this.appendIfSupplied(e,"webchat[appearance][header][name]",t.header?.name),this.appendIfSupplied(e,"webchat[appearance][launcher][icon_url]",t.launcher?.iconUrl),this.appendIfSupplied(e,"webchat[handoff][identifier]",s.number),this.appendIfSupplied(e,"webchat[handoff][restrict_to_channel]",s.restrictToChannel)}static appendIfSupplied(e,t,s){null!=s&&e.searchParams.append(t,String(s))}},E=class{static get endpoint(){return f.endpoint("public/widgets/whatsapp")}static async get(e){const t=new URL(`${this.endpoint}/${e}`);t.searchParams.append("locale",a.toString()),t.searchParams.append("placement",f.whatsapp.placement),this.appendWhatsAppOverrides(t);const s=await this.fetchWidget(t);if(!s.ok)return null;const i=await this.parseWidgetResponse(s);return i?(wt.business.data||(wt.business.setData(i.business),wt.business.setLocale(i.locale)),(new DOMParser).parseFromString(i.html,"text/html").querySelector("article")):null}static appendWhatsAppOverrides(e){const{appearance:t,body:s,number:i}=f.whatsapp;this.appendIfSupplied(e,"whatsapp[appearance][launcher][icon_url]",t.launcher?.iconUrl),this.appendIfSupplied(e,"whatsapp[number]",i),this.appendIfSupplied(e,"whatsapp[body]",s)}static appendIfSupplied(e,t,s){null!=s&&e.searchParams.append(t,String(s))}static async fetchWidget(e){try{return await fetch(e,{method:"GET",headers:wt.headers})}catch(e){return{ok:!1}}}static async parseWidgetResponse(e){try{return await e.json()}catch(e){return null}}},A=class{static get endpoint(){return f.endpoint("public/acks")}static async send(e={}){const t={...e,session:wt.session,at:(new Date).toISOString()};fetch(this.endpoint,{method:"POST",headers:wt.headers,body:JSON.stringify(t),keepalive:!0})}},x=class{static get endpoint(){return f.endpoint("public/push/identities")}static async create(e={}){const t=await fetch(this.endpoint,{method:"POST",keepalive:!0,headers:wt.headers,body:JSON.stringify({...e,session:wt.session,origin:window.location.origin})});return new v(t.ok,t)}static async destroy(e={}){const t=await fetch(this.endpoint,{method:"DELETE",keepalive:!0,headers:wt.headers,body:JSON.stringify({...e,session:wt.session,origin:window.location.origin})});return new v(t.ok,t)}},M=class{static get endpoint(){return f.endpoint("public/push/alerts")}static async create({section:e,kind:t,page:s}){const i=await fetch(this.endpoint,{method:"POST",keepalive:!0,headers:wt.headers,body:JSON.stringify({session:wt.session,section:e,kind:t,page:s})});return new v(i.ok,i)}};function k(e){const t=JSON.stringify(e);return"undefined"==typeof Blob?t.length<6e4:new Blob([t]).size<6e4}class I{static get businesses(){return b}static get events(){return w}static get forms(){return T}static get popups(){return C}static get popups(){return C}static get webchats(){return O}static get whatsappWidgets(){return E}static get identifications(){return S}static get acks(){return A}static get pushAlerts(){return M}static get pushIdentities(){return x}}const L={en:{white_label:{powered_by:"By"},errors:{parameter_not_unique:"This value is taken.",blank:"This field is required."},forms:{phone:"Click the link sent via SMS to verify your submission.",email:"Click the link sent via email to verify your submission.",phone_and_email:"Click the links sent via SMS and email to verify your submission.",none:"Your submission has been received."}},es:{white_label:{powered_by:"Por"},errors:{parameter_not_unique:"Este valor ya está en uso.",blank:"Este campo es obligatorio."},forms:{phone:"Haga clic en el enlace enviado por SMS para verificar su envío.",email:"Haga clic en el enlace enviado por e-mail para verificar su envío.",phone_and_email:"Haga clic en los enlaces enviados por SMS y e-mail para verificar su envío.",none:"Su envío ha sido recibido."}}},P="data-hellotext-stylesheet";class _{constructor(e){this.id=e,this.data=null,this.stylesheet=null,this.stylesheetLoaded=Promise.resolve(!1)}async hydrate(){try{const e=await b.get(this.id);if(!1===e.ok)return null;const t=await e.json();return t?(this.setData(t),t.locale&&this.setLocale(t.locale),t):null}catch(e){return null}}setData(e){this.data=e,"undefined"!=typeof document&&e.style_url?(this.stylesheet=this.constructor.ensureStylesheet(e.style_url),this.stylesheetLoaded=this.constructor.waitForStylesheet(this.stylesheet)):(this.stylesheet=null,this.stylesheetLoaded=Promise.resolve(!1))}static get stylesheetSelector(){return`link[rel="stylesheet"][${P}]`}static ensureStylesheet(e){const t=this.normalizedStylesheetUrl(e),s=this.stylesheetLinks.find(e=>e.href===t);if(s)return s.setAttribute(P,"true"),s;const i=document.createElement("link");return i.rel="stylesheet",i.href=e,i.setAttribute(P,"true"),this.waitForStylesheet(i),document.head.append(i),i}static get stylesheetLinks(){return"undefined"==typeof document?[]:Array.from(document.querySelectorAll(this.stylesheetSelector))}static get latestStylesheet(){return this.stylesheetLinks[this.stylesheetLinks.length-1]}static normalizedStylesheetUrl(e){try{return new URL(e,document.baseURI).href}catch(t){return e}}static waitForStylesheet(e){return e?this.stylesheetIsLoaded(e)?Promise.resolve(!0):"false"===e.dataset.hellotextStylesheetLoaded?Promise.resolve(!1):(e._hellotextStylesheetLoaded||(e._hellotextStylesheetLoaded=new Promise(t=>{let s;const i=i=>{clearTimeout(s),e.removeEventListener("load",n),e.removeEventListener("error",r),e.dataset.hellotextStylesheetLoaded=i?"true":"false",t(i)},n=()=>i(this.stylesheetIsLoaded(e)),r=()=>i(!1);e.addEventListener("load",n),e.addEventListener("error",r),s=setTimeout(()=>i(this.stylesheetIsLoaded(e)),1e4),s.unref&&s.unref()})),e._hellotextStylesheetLoaded):Promise.resolve(!1)}static stylesheetIsLoaded(e){return"true"===e.dataset.hellotextStylesheetLoaded||!!e.sheet}get subscription(){return this.data.subscription}get country(){return this.data.country}get enabledWhitelist(){return"disabled"!==this.data.whitelist}setLocale(e){if(!L[e])return console.warn(`Locale ${e} not found`);this.data||(this.data={}),this.data.locale=e}get locale(){return L[this.data.locale]}get features(){return this.data.features}}class N{static set(e,t){if("undefined"!=typeof document){const s="https:"===window.location.protocol?"; Secure":"",i=F.getRootDomain(),n=31536e4;document.cookie=i?`${e}=${t}; path=/${s}; domain=${i}; max-age=${n}; SameSite=Lax`:`${e}=${t}; path=/${s}; max-age=${n}; SameSite=Lax`}return"hello_session"===e&&wt.eventEmitter.dispatch("session-set",t),"hello_utm"===e&&wt.eventEmitter.dispatch("utm-set",t),t}static get(e){return"undefined"!=typeof document?document.cookie.match("(^|;)\\s*"+e+"\\s*=\\s*([^;]+)")?.pop():void 0}static delete(e){if("undefined"!=typeof document){const t=F.getRootDomain(),s="https:"===window.location.protocol?"; Secure":"";document.cookie=t?`${e}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT${s}; domain=${t}; SameSite=Lax`:`${e}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT${s}; SameSite=Lax`}}}class D{constructor(){const e=new URLSearchParams(window.location.search),t={source:e.get("utm_source"),medium:e.get("utm_medium"),campaign:e.get("utm_campaign"),term:e.get("utm_term"),content:e.get("utm_content")};this.save(t)}save(e){if(!e.source||!e.medium)return;const t=Object.fromEntries(Object.entries(e).filter(([e,t])=>t));t.observed_at=(new Date).toISOString(),N.set("hello_utm",JSON.stringify(t))}get current(){try{return JSON.parse(N.get("hello_utm"))||{}}catch(e){return{}}}}class F{constructor(e=null){this.utm=new D,this._url=e}get url(){return null!==this._url&&void 0!==this._url?this._url:window.location.href}get title(){return document.title}get path(){if(this._url)try{return new URL(this._url).pathname}catch(e){return"/"}return window.location.pathname}get utmParams(){return this.utm.current}get trackingData(){return{page:{url:this.url,title:this.title,path:this.path},utm_params:this.utmParams}}get domain(){try{const e=this.url;if(!e)return null;const t=new URL(e).hostname;return F.getRootDomain(t)}catch(e){return null}}static getRootDomain(e=null){try{if(!e){if("undefined"==typeof window||!window.location?.hostname)return null;e=window.location.hostname}const t=e.split(".");if(t.length<=1)return e;const s=["myshopify.com","vtexcommercestable.com.br","myvtex.com","wixsite.com"];for(const e of s){const s=e.split(".");if(t.slice(-s.length).join(".")===e&&t.length>s.length)return`.${t.slice(-(s.length+1)).join(".")}`}const i=t[t.length-1],n=t[t.length-2];return t.length>2&&2===i.length&&n.length<=3?`.${t.slice(-3).join(".")}`:`.${t.slice(-2).join(".")}`}catch(e){return null}}}class R{static#n;static#r;static#o;static get session(){return this.#n}static get ackPayload(){return{utm_params:this.#o?.utmParams||{}}}static set session(e){const t=N.get("hello_session");return this.#n=e,N.set("hello_session",e),t!==e&&N.delete("hello_session_ack_at"),N.get("hello_session_ack_at")||(I.acks.send(this.ackPayload),N.set("hello_session_ack_at",(new Date).toISOString())),this.#n}static initialize(e=new F){this.#o=e,this.#r=new y,this.session=this.#r.session||f.session||N.get("hello_session"),!this.session&&f.autoGenerateSession&&(this.session=crypto.randomUUID())}}class B{static build(e){const t=document.createElement("article"),s=document.createElement("label"),i=document.createElement("input");s.innerText=e.label,i.type=e.type,i.required=e.required,i.placeholder=e.placeholder,["first_name","last_name"].includes(e.kind)?(i.type="text",i.id=i.name=e.kind,s.setAttribute("for",e.kind)):(i.type=e.type,"email"===e.type?(i.id=i.name="email",s.setAttribute("for","email")):"tel"===i.type?(i.id=i.name="phone",s.setAttribute("for","phone"),i.value=`+${wt.business.country.prefix}`,i.setAttribute("data-default-value",`+${wt.business.country.prefix}`)):(i.name=i.id=`property_by_id[${e.property}]`,s.setAttribute("for",`property_by_id[${e.property}]`)));const n=document.createElement("main");n.appendChild(s),n.appendChild(i),t.appendChild(n),t.setAttribute("data-hellotext--form-target","inputContainer"),i.setAttribute("data-hellotext--form-target","input");const r=document.createElement("div");return r.setAttribute("data-error-container",""),t.appendChild(r),t}}class ${static build(){const e=document.createElement("div");return e.innerHTML=this.#a(),e.firstElementChild}static#a(){const e=`https://www.hellotext.com?hello_session=${wt.session}`;return`\n
\n ${wt.business.locale.white_label.powered_by}\n\n \n \n Hellotext\n \n \n \n
\n `}}function j(e,t){(null==t||t>e.length)&&(t=e.length);for(var s=0,i=Array(t);s2?s-2:0),n=2;n1?t-1:0),i=1;i1?s-1:0),n=1;n2&&void 0!==arguments[2]?arguments[2]:re;if(U&&U(e,null),!ne(t))return e;let i=t.length;for(;i--;){let n=t[i];if("string"==typeof n){const e=s(n);e!==n&&(z(t)||(t[i]=e),n=e)}e[n]=!0}return e}function Se(e){for(let t=0;t/g),Be=G(/\${[\w\W]*/g),$e=G(/^data-[\-\w.\u00B7-\uFFFF]+$/),je=G(/^aria-[\-\w]+$/),Ve=G(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),qe=G(/^(?:\w+script|data):/i),Ue=G(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),ze=G(/^html$/i),We=G(/^[a-z][.\w]*(-[.\w]+)+$/i),He=G(/<[/\w!]/g),Ke=G(/<[/\w]/g),Ge=G(/<\/no(script|embed|frames)/i),Je=G(/\/>/i),Ye=["style","script","xmp","iframe","noembed","noframes","plaintext","noscript"],Ze=K(Te({},Ye)),Xe=function(){const e={};return Q(Ye,t=>{e[t]=G(new RegExp("])","i"))}),K(e)}(),Qe=function(){return"undefined"==typeof window?null:window},et=function(e,t,s,i){return ge(e,t)&&ne(e[t])?Te(i.base?Ce(i.base):{},e[t],i.transform):s},tt=function(e,t,s){const i=ge(e,t)?e[t]:void 0;return i&&"object"==typeof i?Ce(i):s()};var st=function e(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:Qe();const s=t=>e(t);if(s.version="3.4.14",s.removed=[],!t||!t.document||9!==t.document.nodeType||!t.Element)return s.isSupported=!1,s;let i=t.document;const n=i,r=n.currentScript;t.DocumentFragment;const o=t.HTMLTemplateElement,a=t.Node,c=t.Element,l=t.NodeFilter;void 0===t.NamedNodeMap&&(t.NamedNodeMap||t.MozNamedAttrMap),t.HTMLFormElement;const h=t.DOMParser,u=t.trustedTypes,d=c.prototype,p=Oe(d,"cloneNode"),m=Oe(d,"remove"),g=Oe(d,"nextSibling"),f=Oe(d,"childNodes"),b=Oe(d,"parentNode"),y=Oe(d,"shadowRoot"),v=Oe(d,"attributes"),w=a&&a.prototype?Oe(a.prototype,"nodeType"):null,T=a&&a.prototype?Oe(a.prototype,"nodeName"):null,S=a&&a.prototype?Oe(a.prototype,"ownerDocument"):null,C=function(e){return w?w(e):e.nodeType},O=function(e){return T?T(e):e.nodeName};if("function"==typeof o){const e=i.createElement("template");e.content&&e.content.ownerDocument&&(i=e.content.ownerDocument)}let E,A,x="",M=!1,k=0;const I=function(){if(k>0)throw ye('A configured TRUSTED_TYPES_POLICY callback (createHTML or createScriptURL) must not call DOMPurify.sanitize, as that causes infinite recursion. Do not pass a policy whose callbacks wrap DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted Types" section of the README.')},L=function(e){I(),k++;try{return E.createHTML(e)}finally{k--}},P=i,_=P.implementation,N=P.createNodeIterator,D=P.createDocumentFragment,F=P.getElementsByTagName,R=n.importNode;let B={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]};s.isSupported="function"==typeof q&&"function"==typeof b&&_&&void 0!==_.createHTMLDocument;const $=Fe,j=Re,V=Be,U=$e,z=je,W=qe,H=Ue,Y=We;let Z=Ve,X=null;const ve=Te({},[...Ee,...Ae,...xe,...ke,...Le]);let we=null;const Se=Te({},[...Pe,..._e,...Ne,...De]);let Ye=Object.seal(J(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),st=null,it=null;const nt=Object.seal(J(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}}));let rt=!0,ot=!0,at=!1,ct=!0,lt=!1,ht=!0,ut=!1,dt=!1,pt=null,mt=null,gt=!1,ft=!1,bt=!1,yt=!1,vt=!0,wt=!1;const Tt="user-content-";let St=!0,Ct=!1,Ot={},Et=null;const At=Te({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","selectedcontent","style","svg","template","thead","title","video","xmp"]);let xt=null;const Mt=Te({},["audio","video","img","source","image","track"]);let kt=null;const It=Te({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Lt="http://www.w3.org/1998/Math/MathML",Pt="http://www.w3.org/2000/svg",_t="http://www.w3.org/1999/xhtml";let Nt=_t,Dt=!1,Ft=null;const Rt=Te({},[Lt,Pt,_t],oe),Bt=K(["mi","mo","mn","ms","mtext"]);let $t=Te({},Bt);const jt=K(["annotation-xml"]);let Vt=Te({},jt);const qt=Te({},["title","style","font","a","script"]);let Ut=null;const zt=["application/xhtml+xml","text/html"];let Wt=null,Ht=null;const Kt=i.createElement("form"),Gt=function(e){return e instanceof RegExp||e instanceof Function},Jt=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(Ht&&Ht===e)return;e&&"object"==typeof e||(e={}),e=Ce(e),Ut=-1===zt.indexOf(e.PARSER_MEDIA_TYPE)?"text/html":e.PARSER_MEDIA_TYPE,Wt="application/xhtml+xml"===Ut?oe:re,X=et(e,"ALLOWED_TAGS",ve,{transform:Wt}),we=et(e,"ALLOWED_ATTR",Se,{transform:Wt}),Ft=et(e,"ALLOWED_NAMESPACES",Rt,{transform:oe}),kt=et(e,"ADD_URI_SAFE_ATTR",It,{transform:Wt,base:It}),xt=et(e,"ADD_DATA_URI_TAGS",Mt,{transform:Wt,base:Mt}),Et=et(e,"FORBID_CONTENTS",At,{transform:Wt}),st=et(e,"FORBID_TAGS",Ce({}),{transform:Wt}),it=et(e,"FORBID_ATTR",Ce({}),{transform:Wt}),Ot=!!ge(e,"USE_PROFILES")&&(e.USE_PROFILES&&"object"==typeof e.USE_PROFILES?Ce(e.USE_PROFILES):e.USE_PROFILES),rt=!1!==e.ALLOW_ARIA_ATTR,ot=!1!==e.ALLOW_DATA_ATTR,at=e.ALLOW_UNKNOWN_PROTOCOLS||!1,ct=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,lt=e.SAFE_FOR_TEMPLATES||!1,ht=!1!==e.SAFE_FOR_XML,ut=e.WHOLE_DOCUMENT||!1,ft=e.RETURN_DOM||!1,bt=e.RETURN_DOM_FRAGMENT||!1,yt=e.RETURN_TRUSTED_TYPE||!1,gt=e.FORCE_BODY||!1,vt=!1!==e.SANITIZE_DOM,wt=e.SANITIZE_NAMED_PROPS||!1,St=!1!==e.KEEP_CONTENT,Ct=e.IN_PLACE||!1,Z=function(e){try{return be(e,""),!0}catch(e){return!1}}(e.ALLOWED_URI_REGEXP)?e.ALLOWED_URI_REGEXP:Ve,Nt="string"==typeof e.NAMESPACE?e.NAMESPACE:_t,$t=tt(e,"MATHML_TEXT_INTEGRATION_POINTS",()=>Te({},Bt)),Vt=tt(e,"HTML_INTEGRATION_POINTS",()=>Te({},jt));const t=tt(e,"CUSTOM_ELEMENT_HANDLING",()=>J(null));if(Ye=J(null),ge(t,"tagNameCheck")&&Gt(t.tagNameCheck)&&(Ye.tagNameCheck=t.tagNameCheck),ge(t,"attributeNameCheck")&&Gt(t.attributeNameCheck)&&(Ye.attributeNameCheck=t.attributeNameCheck),ge(t,"allowCustomizedBuiltInElements")&&"boolean"==typeof t.allowCustomizedBuiltInElements&&(Ye.allowCustomizedBuiltInElements=t.allowCustomizedBuiltInElements),G(Ye),lt&&(ot=!1),bt&&(ft=!0),Ot&&(X=Te({},Le),we=J(null),!0===Ot.html&&(Te(X,Ee),Te(we,Pe)),!0===Ot.svg&&(Te(X,Ae),Te(we,_e),Te(we,De)),!0===Ot.svgFilters&&(Te(X,xe),Te(we,_e),Te(we,De)),!0===Ot.mathMl&&(Te(X,ke),Te(we,Ne),Te(we,De))),nt.tagCheck=null,nt.attributeCheck=null,ge(e,"ADD_TAGS")&&("function"==typeof e.ADD_TAGS?nt.tagCheck=e.ADD_TAGS:ne(e.ADD_TAGS)&&(X===ve&&(X=Ce(X)),Te(X,e.ADD_TAGS,Wt))),ge(e,"ADD_ATTR")&&("function"==typeof e.ADD_ATTR?nt.attributeCheck=e.ADD_ATTR:ne(e.ADD_ATTR)&&(we===Se&&(we=Ce(we)),Te(we,e.ADD_ATTR,Wt))),ge(e,"ADD_FORBID_CONTENTS")&&ne(e.ADD_FORBID_CONTENTS)&&(Et===At&&(Et=Ce(Et)),Te(Et,e.ADD_FORBID_CONTENTS,Wt)),St&&(X["#text"]=!0),ut&&Te(X,["html","head","body"]),X.table&&(Te(X,["tbody"]),delete st.tbody),e.TRUSTED_TYPES_POLICY){if("function"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw ye('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw ye('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');const t=E;E=e.TRUSTED_TYPES_POLICY;try{x=L("")}catch(e){throw E=t,e}}else null===e.TRUSTED_TYPES_POLICY?(E=void 0,x=""):(void 0===E&&(M||(A=function(e,t){if("object"!=typeof e||"function"!=typeof e.createPolicy)return null;let s=null;const i="data-tt-policy-suffix";t&&t.hasAttribute(i)&&(s=t.getAttribute(i));const n="dompurify"+(s?"#"+s:"");try{return e.createPolicy(n,{createHTML:e=>e,createScriptURL:e=>e})}catch(e){return console.warn("TrustedTypes policy "+n+" could not be created."),null}}(u,r),M=!0),E=A),E&&"string"==typeof x&&(x=L("")));K&&K(e),Ht=e},Yt=Te({},[...Ae,...xe,...Me]),Zt=Te({},[...ke,...Ie]),Xt=function(e){se(s.removed,{element:e});try{b(e).removeChild(e)}catch(t){if(m(e),!b(e))throw ye("a node selected for removal could not be detached from its tree and cannot be safely returned; refusing to sanitize in place")}},Qt=function(e,t,s){try{e.removeAttributeNode(t)}catch(t){try{e.removeAttribute(s)}catch(e){}}},es=function(e){is(e);const t=f(e);if(t){const e=[];Q(t,t=>{se(e,t)}),Q(e,e=>{try{m(e)}catch(e){}})}const s=v(e);if(s)for(let t=s.length-1;t>=0;--t){const i=s[t],n=i&&i.name;"string"==typeof n&&Qt(e,i,n)}},ts=function(e,t,i){if(!i)try{i=t.getAttributeNode(e)}catch(e){i=null}se(s.removed,{attribute:i||null,from:t});try{i?t.removeAttributeNode(i):t.removeAttribute(e)}catch(s){try{t.removeAttribute(e)}catch(e){}}if("is"===e)if(ft||bt)try{Xt(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},ss=function(e){const t=v(e);if(t)for(let s=t.length-1;s>=0;--s){const i=t[s],n=i&&i.name;"string"!=typeof n||we[Wt(n)]||Qt(e,i,n)}},is=function(e){const t=[e];for(;t.length>0;){const e=t.pop();1===C(e)&&ss(e);const s=f(e);if(s)for(let e=s.length-1;e>=0;--e)t.push(s[e])}},ns=function(e,t){return!!ht&&("patchsrc"===e||"for"===e&&"label"!==t&&"output"!==t)},rs=function(e){let t=null,s=null;if(gt)e=""+e;else{const t=ae(e,/^[\r\n\t ]+/);s=t&&t[0]}"application/xhtml+xml"===Ut&&Nt===_t&&(e=''+e+"");const n=E?L(e):e;if(Nt===_t)try{t=(new h).parseFromString(n,Ut)}catch(e){}if(!t||!t.documentElement){t=_.createDocument(Nt,"template",null);try{t.documentElement.innerHTML=Dt?x:n}catch(e){}}const r=t.body||t.documentElement;return e&&s&&r.insertBefore(i.createTextNode(s),r.childNodes[0]||null),Nt===_t?F.call(t,ut?"html":"body")[0]:ut?t.documentElement:r},os=function(e){const t=S?S(e):e.ownerDocument;return N.call(t||e,e,l.SHOW_ELEMENT|l.SHOW_COMMENT|l.SHOW_TEXT|l.SHOW_PROCESSING_INSTRUCTION|l.SHOW_CDATA_SECTION,null)},as=function(e){return e=ce(e,$," "),e=ce(e,j," "),ce(e,V," ")},cs=function(e){var t;e.normalize();const s=S?S(e):e.ownerDocument,i=N.call(s||e,e,l.SHOW_TEXT|l.SHOW_COMMENT|l.SHOW_CDATA_SECTION|l.SHOW_PROCESSING_INSTRUCTION,null);let n=i.nextNode();for(;n;)n.data=as(n.data),n=i.nextNode();const r=null===(t=e.querySelectorAll)||void 0===t?void 0:t.call(e,"template");r&&Q(r,e=>{hs(e.content)&&cs(e.content)})},ls=function(e){const t=T?T(e):null;return"string"==typeof t&&"form"===Wt(t)&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||e.attributes!==v(e)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes||e.nodeType!==w(e)||e.childNodes!==f(e))},hs=function(e){if(!w||"object"!=typeof e||null===e)return!1;try{return 11===w(e)}catch(e){return!1}},us=function(e){if(!w||"object"!=typeof e||null===e)return!1;try{return"number"==typeof w(e)}catch(e){return!1}};function ds(e,t,i){0!==e.length&&Q(e,e=>{e.call(s,t,i,Ht)})}const ps=function(e,t){if(e instanceof RegExp)return be(e,t);if(e instanceof Function){for(var s=arguments.length,i=new Array(s>2?s-2:0),n=2;n=0;--n){const r=e===s?p(i[n],!0):i[n];t.insertBefore(r,g(e))}}return Xt(e),!0}(e,i,t);return!1===s&&ds(B.afterSanitizeElements,e,null),s}if(1===C(e)&&!function(e){let t=b(e);t&&t.tagName||(t={namespaceURI:Nt,tagName:"template"});const s=re(e.tagName),i=re(t.tagName);return!!Ft[e.namespaceURI]&&(e.namespaceURI===Pt?function(e,t,s){return t.namespaceURI===_t?"svg"===e:t.namespaceURI===Lt?"svg"===e&&("annotation-xml"===s||$t[s]):Boolean(Yt[e])}(s,t,i):e.namespaceURI===Lt?function(e,t,s){return t.namespaceURI===_t?"math"===e:t.namespaceURI===Pt?"math"===e&&Vt[s]:Boolean(Zt[e])}(s,t,i):e.namespaceURI===_t?function(e,t,s){return!(t.namespaceURI===Pt&&!Vt[s])&&!(t.namespaceURI===Lt&&!$t[s])&&!Zt[e]&&(qt[e]||!Yt[e])}(s,t,i):!("application/xhtml+xml"!==Ut||!Ft[e.namespaceURI]))}(e))return Xt(e),!0;if(("noscript"===i||"noembed"===i||"noframes"===i)&&be(Ge,e.innerHTML))return Xt(e),!0;if(lt&&3===e.nodeType){const t=as(e.textContent);e.textContent!==t&&(se(s.removed,{element:e.cloneNode()}),e.textContent=t)}return ds(B.afterSanitizeElements,e,null),!1},bs=function(e,t,s){if(it[t])return!1;if(ns(t,e))return!1;if(vt&&("id"===t||"name"===t)&&(s in i||s in Kt))return!1;const n=we[t]||nt.attributeCheck instanceof Function&&nt.attributeCheck(t,e);return!(!ot||!be(U,t))||!(!rt||!be(z,t))||(n?!(!kt[t]&&!be(Z,ce(s,H,""))&&("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==le(s,"data:")||!xt[e])&&(!at||be(W,ce(s,H,"")))&&s):vs(e)&&ps(Ye.tagNameCheck,e)&&ps(Ye.attributeNameCheck,t,e)||"is"===t&&Ye.allowCustomizedBuiltInElements&&ps(Ye.tagNameCheck,s))},ys=Te({},["annotation-xml","color-profile","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","missing-glyph"]),vs=function(e){return!ys[re(e)]&&be(Y,e)},ws=function(e,t,s,i){if(E&&"object"==typeof u&&"function"==typeof u.getAttributeType&&!s)switch(u.getAttributeType(e,t)){case"TrustedHTML":return L(i);case"TrustedScriptURL":return function(e){I(),k++;try{return E.createScriptURL(e)}finally{k--}}(i)}return i},Ts=function(e,t,i,n){try{i?e.setAttributeNS(i,t,n):e.setAttribute(t,n),ls(e)?Xt(e):te(s.removed)}catch(s){ts(t,e)}},Ss=function(e){ds(B.beforeSanitizeAttributes,e,null);const t=e.attributes;if(!t||ls(e))return;we=ms(B.uponSanitizeAttribute,we,Se,mt);const s={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:we,forceKeepAttr:void 0};let i=t.length;const n=Wt(e.nodeName);for(;i--;){const r=t[i],o=r.name,a=r.namespaceURI,c=r.value,l=Wt(o),h=c;let u="value"===o?h:he(h);s.attrName=l,s.attrValue=u,s.keepAttr=!0,s.forceKeepAttr=void 0,ds(B.uponSanitizeAttribute,e,s),u=s.attrValue,!wt||"id"!==l&&"name"!==l||0===le(u,Tt)||(ts(o,e,r),u=Tt+u),ht&&be(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i,u)||"attributename"===l&&ae(u,"href")?ts(o,e,r):s.forceKeepAttr||(!s.keepAttr||!ct&&be(Je,u)?ts(o,e,r):(lt&&(u=as(u)),bs(n,l,u)?(u=ws(n,l,a,u),u!==h&&Ts(e,o,a,u)):ts(o,e,r)))}ds(B.afterSanitizeAttributes,e,null)},Cs=function(e){let t=null;const s=os(e);for(ds(B.beforeSanitizeShadowDOM,e,null);t=s.nextNode();)if(ds(B.uponSanitizeShadowNode,t,null),fs(t,e),Ss(t),hs(t.content)&&Cs(t.content),1===C(t)){const e=y(t);hs(e)&&(Os(e),Cs(e))}ds(B.afterSanitizeShadowDOM,e,null)},Os=function(e){const t=[{node:e,shadow:null}];for(;t.length>0;){const e=t.pop();if(e.shadow){Cs(e.shadow);continue}const s=e.node,i=1===C(s),n=f(s);if(n)for(let e=n.length-1;e>=0;--e)t.push({node:n[e],shadow:null});if(i){const e=T?T(s):null;if("string"==typeof e&&"template"===Wt(e)){const e=s.content;hs(e)&&t.push({node:e,shadow:null})}}if(i){const e=y(s);hs(e)&&t.push({node:null,shadow:e},{node:e,shadow:null})}}};return s.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=null,r=null,o=null,a=null;if(Dt=!e,Dt&&(e="\x3c!--\x3e"),"string"!=typeof e&&!us(e)&&"string"!=typeof(e=function(e){switch(typeof e){case"string":return e;case"number":return ue(e);case"boolean":return de(e);case"bigint":return pe?pe(e):"0";case"symbol":return me?me(e):"Symbol()";case"undefined":default:return fe(e);case"function":case"object":{if(null===e)return fe(e);const t=e,s=Oe(t,"toString");if("function"==typeof s){const e=s(t);return"string"==typeof e?e:fe(e)}return fe(e)}}}(e)))throw ye("dirty is not a string, aborting");if(!s.isSupported)return e;dt?(X=pt,we=mt):Jt(t),(B.uponSanitizeElement.length>0||B.uponSanitizeAttribute.length>0)&&(X=Ce(X)),B.uponSanitizeAttribute.length>0&&(we=Ce(we)),s.removed=[];const c=Ct&&"string"!=typeof e&&us(e);if(c){!function(e){if(!ht)return;const t=[e];for(;t.length>0;){const e=t.pop(),s=C(e);if(7===s||8===s&&be(Ke,e.data)){try{m(e)}catch(e){}continue}if(1===s){const t=e,s=Wt(O(e));try{t.hasAttribute&&t.hasAttribute("patchsrc")&&t.removeAttribute("patchsrc"),t.hasAttribute&&t.hasAttribute("for")&&ns("for",s)&&t.removeAttribute("for")}catch(e){}}const i=f(e);if(i)for(let e=i.length-1;e>=0;--e)t.push(i[e])}}(e);const t=O(e);if("string"==typeof t){const s=Wt(t);if(!X[s]||st[s])throw es(e),ye("root node is forbidden and cannot be sanitized in-place")}if(ls(e))throw es(e),ye("root node is clobbered and cannot be sanitized in-place");try{Os(e)}catch(t){throw es(e),t}}else if(us(e))i=rs("\x3c!----\x3e"),r=i.ownerDocument.importNode(e,!0),1===r.nodeType&&"BODY"===r.nodeName||"HTML"===r.nodeName?i=r:i.appendChild(r),Os(r);else{if(!ft&&!lt&&!ut&&-1===e.indexOf("<"))return E&&yt?L(e):e;if(i=rs(e),!i)return ft?null:yt?x:""}i&>&&Xt(i.firstChild);const l=c?e:i;try{const e=os(l);for(;o=e.nextNode();)fs(o,l),Ss(o),hs(o.content)&&Cs(o.content)}catch(t){throw c&&(es(e),Q(s.removed,e=>{e.element&&is(e.element)})),t}if(c)return Q(s.removed,e=>{e.element&&is(e.element)}),lt&&cs(e),e;if(ft){if(lt&&cs(i),bt)for(a=D.call(i.ownerDocument);i.firstChild;)a.appendChild(i.firstChild);else a=i;return(we.shadowroot||we.shadowrootmode)&&(a=R.call(n,a,!0)),a}let h=ut?i.outerHTML:i.innerHTML;return ut&&X["!doctype"]&&i.ownerDocument&&i.ownerDocument.doctype&&i.ownerDocument.doctype.name&&be(ze,i.ownerDocument.doctype.name)&&(h="\n"+h),lt&&(h=as(h)),E&&yt?L(h):h},s.setConfig=function(){Jt(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),dt=!0,pt=X,mt=we},s.clearConfig=function(){Ht=null,dt=!1,pt=null,mt=null,E=A,x=""},s.isValidAttribute=function(e,t,s){Ht||Jt({});const i=Wt(e),n=Wt(t);return bs(i,n,s)},s.addHook=function(e,t){"function"==typeof t&&ge(B,e)&&se(B[e],t)},s.removeHook=function(e,t){if(ge(B,e)){if(void 0!==t){const s=ee(B[e],t);return-1===s?void 0:ie(B[e],s,1)[0]}return te(B[e])}},s.removeHooks=function(e){ge(B,e)&&(B[e]=[])},s.removeAllHooks=function(){B={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},s}();const it={ADD_ATTR:["target"],ALLOW_DATA_ATTR:!1,RETURN_DOM_FRAGMENT:!0,USE_PROFILES:{html:!0}},nt={ADD_ATTR:["target"],ADD_TAGS:["hellotext-icon"],RETURN_DOM_FRAGMENT:!0,USE_PROFILES:{html:!0,svg:!0}};function rt(e,t){const s=st.sanitize(e,t);return s.querySelectorAll('a[target="_blank"]').forEach(e=>{const t=new Set(e.rel.split(/\s+/).filter(Boolean));t.add("noopener"),t.add("noreferrer"),e.rel=Array.from(t).join(" ")}),s}function ot(e,t){e.replaceChildren(function(e){return rt(e,it)}(t))}class at{constructor(e,t=null){this.data=e,this.element=t||document.querySelector(`[data-hello-form="${this.id}"]`)||document.createElement("form")}async mount({ifCompleted:e=!0}={}){if(e&&this.hasBeenCompleted)return this.element?.remove(),wt.eventEmitter.dispatch("form:completed",{id:this.id,...JSON.parse(localStorage.getItem(`hello-form-${this.id}`))});const t=this.data.steps[0];this.buildHeader(t.header),this.buildInputs(t.inputs),this.buildButton(t.button),this.buildFooter(t.footer),this.elementAttributes.forEach(e=>{this.element.setAttribute(e.name,e.value)}),document.contains(this.element)||document.body.appendChild(this.element),wt.business.features.white_label||this.element.prepend($.build())}buildHeader(e){const t=this.#c("[data-form-header]","header");ot(t,e.content),this.element.querySelector("[data-form-header]")?this.element.querySelector("[data-form-header]").replaceWith(t):this.element.prepend(t)}buildInputs(e){const t=this.#c("[data-form-inputs]","main");e.map(e=>B.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)}buildButton(e){const t=this.#c("[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)}buildFooter(e){const t=this.#c("[data-form-footer]","footer");ot(t,e.content),this.element.querySelector("[data-form-footer]")?this.element.querySelector("[data-form-footer]").replaceWith(t):this.element.appendChild(t)}markAsCompleted(e){const t={state:"completed",id:this.id,data:e,completedAt:(new Date).getTime()};localStorage.setItem(`hello-form-${this.id}`,JSON.stringify(t)),wt.eventEmitter.dispatch("form:completed",t)}get hasBeenCompleted(){return null!==localStorage.getItem(`hello-form-${this.id}`)}get id(){return this.data.id}get localeAuthKey(){const 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"}get elementAttributes(){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)}]}#c(e,t){const s=this.element.querySelector(e);if(s)return s.cloneNode(!0);const i=document.createElement(t);return i.setAttribute(e.replace("[","").replace("]",""),""),i}}class ct extends Error{constructor(){super("You need to initialize before tracking events. Call Hellotext.initialize and pass your public business id"),this.name="NotInitializedError"}}class lt{constructor(){this.forms=[],this.includes=this.includes.bind(this),this.excludes=this.excludes.bind(this),this.add=this.add.bind(this),"undefined"!=typeof MutationObserver&&(this.mutationObserver=new MutationObserver(this.formMutationObserver.bind(this)),this.mutationObserver.observe(document.body,{childList:!0,subtree:!0}))}collectExistingFormsOnPage(){Array.from(document.querySelectorAll("[data-hello-form]")).length>0&&this.collect()}formMutationObserver(e){e.find(e=>"childList"===e.type&&e.addedNodes.length>0)&&Array.from(document.querySelectorAll("[data-hello-form]")).length>0&&this.collect()}async collect(){if(wt.notInitialized)throw new ct;if(this.fetching)return;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.");const e=this.#l;if(0===e.length)return;const t=e.map(e=>T.get(e).then(e=>e.json()));this.fetching=!0,await Promise.all(t).then(e=>e.forEach(this.add)).then(()=>wt.eventEmitter.dispatch("forms:collected",this)).then(()=>this.fetching=!1),f.forms.autoMount&&this.forms.forEach(e=>e.mount())}forEach(e){this.forms.forEach(e)}map(e){return this.forms.map(e)}add(e){this.includes(e.id)||(wt.business.data||(wt.business.setData(e.business),wt.business.setLocale(a.toString())),wt.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 at(e)))}getById(e){return this.forms.find(t=>t.id===e)}getByIndex(e){return this.forms[e]}includes(e){return this.forms.some(t=>t.id===e)}excludes(e){return!this.includes(e)}get length(){return this.forms.length}get#l(){return Array.from(document.querySelectorAll("[data-hello-form]")).map(e=>e.dataset.helloForm).filter(this.excludes)}}class ht{constructor(e){this.publicKey=e.public_key,this.serviceWorkerUrl=f.push.serviceWorkerUrl,this.channelId=f.push.channelId,this.ready=null,this.registrationPromise=null,this.subscribePromise=null,this.unsubscribePromise=null,this.syncPromise=null,this.subscription=null,this.retryTimeout=null,this.retryAttempts=0,this.disposed=!1}initialize(){return this.ready=this.restoreSubscription(),this.ready}async restoreSubscription(){const e=await this.getRegistration(),t=await e.pushManager.getSubscription();!this.disposed&&!this.unsubscribePromise&&t&&this.owns(t)&&(this.subscription=t,await this.sync())}subscribe(){if(this.disposed)return Promise.resolve();if(this.unsubscribePromise)return Promise.reject(new Error("Push unsubscribe is in progress"));if(this.subscribePromise)return this.subscribePromise;const e="default"===Notification.permission?Notification.requestPermission():Promise.resolve(Notification.permission);return this.subscribePromise=this.createSubscription(e).finally(()=>{this.subscribePromise=null}),this.subscribePromise}async createSubscription(e){if("granted"!==await e)throw new Error("Push permission was not granted");const t=await this.getRegistration();if(this.disposed)return;let s=await t.pushManager.getSubscription();if(!this.disposed){if(s&&!this.owns(s))throw new Error("The existing Push subscription belongs to a different application");if(s||=await t.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:this.applicationServerKey}),!this.disposed)return this.subscription=s,this.sync()}}unsubscribe(){return this.disposed?Promise.resolve():(this.unsubscribePromise||(this.clearRetryTimeout(),this.unsubscribePromise=this.removeSubscription().finally(()=>{this.unsubscribePromise=null})),this.unsubscribePromise)}async removeSubscription(){await(this.ready?.catch(()=>{})),await(this.subscribePromise?.catch(()=>{})),await(this.syncPromise?.catch(()=>{}));const e=await this.getRegistration(),t=await e.pushManager.getSubscription()||this.subscription;if(this.disposed)return;if(!t)return null;if(!this.owns(t))throw new Error("The existing Push subscription belongs to a different application");this.subscription=t;const s=await I.pushIdentities.destroy({subscription:t.toJSON()});return s.failed?s:this.disposed?void 0:(await t.unsubscribe(),this.subscription=null,this.clearRetryTimeout(),s)}sync(){return this.disposed?Promise.resolve():(this.syncPromise||(this.clearRetryTimeout(),this.syncPromise=this.registerIdentity().finally(()=>{this.syncPromise=null})),this.syncPromise)}async registerIdentity(){try{const e=await I.pushIdentities.create({subscription:this.subscription.toJSON(),...this.channelId?{channel_id:this.channelId}:{}});return e.succeeded?this.retryAttempts=0:this.scheduleRetry(),e}catch(e){throw this.scheduleRetry(),e}}scheduleRetry(){this.disposed||this.unsubscribePromise||this.retryTimeout||this.retryAttempts>=3||(this.retryTimeout=setTimeout(()=>{this.retryTimeout=null,this.sync().catch(()=>{})},1e3*2**this.retryAttempts),this.retryAttempts+=1)}clearRetryTimeout(){clearTimeout(this.retryTimeout),this.retryTimeout=null}dispose(){this.disposed=!0,this.clearRetryTimeout()}get subscribed(){return!!this.subscription}get applicationServerKey(){const e=this.publicKey.replace(/-/g,"+").replace(/_/g,"/");return Uint8Array.from(atob(e.padEnd(4*Math.ceil(e.length/4),"=")),e=>e.charCodeAt(0))}owns(e){const t=e.options?.applicationServerKey;if(!t)return!1;const s=new Uint8Array(t),i=this.applicationServerKey;return s.length===i.length&&s.every((e,t)=>e===i[t])}getRegistration(){return this.registrationPromise||(this.registrationPromise=this.loadRegistration().catch(e=>{throw this.registrationPromise=null,e})),this.registrationPromise}async loadRegistration(){if(this.serviceWorkerUrl){const e=await navigator.serviceWorker.register(this.serviceWorkerUrl);!e.active||e.installing||e.waiting||await e.update();const t=e.installing||e.waiting||e.active;return"activated"===t?.state?e:new Promise((s,i)=>{const n=setTimeout(()=>r(new Error("Push service worker did not become active")),1e4),r=r=>{clearTimeout(n),t?.removeEventListener("statechange",o),r?i(r):s(e)},o=()=>{"activated"===t?.state&&r(),"redundant"===t?.state&&r(new Error("Push service worker installation failed"))};t?.addEventListener("statechange",o),o()})}return new Promise((e,t)=>{const s=setTimeout(()=>t(new Error("Push service worker is not available")),1e4);navigator.serviceWorker.ready.then(t=>{clearTimeout(s),e(t)},e=>{clearTimeout(s),t(e)})})}static get supported(){return"undefined"!=typeof window&&!0===window.isSecureContext&&"undefined"!=typeof navigator&&"serviceWorker"in navigator&&"undefined"!=typeof PushManager&&"undefined"!=typeof Notification}}class ut{constructor(e,t,s){this.business=t,this.push=s,this.disposed=!1,this.showRequest=0,this.controller=null,this.element=(new DOMParser).parseFromString(e.html,"text/html").querySelector('[data-controller~="hellotext--alert"]'),this.connected=new Promise(e=>{this.resolveConnected=e}),this.onConnect=this.onConnect.bind(this),this.ready=this.render()}onConnect(e){this.controller=e.detail.controller,this.controller.alert=this,this.resolveConnected(!0)}async render(){return!!this.element&&(this.element.addEventListener("hellotext--alert:connected",this.onConnect),!(!await this.business.stylesheetLoaded||this.disposed)&&(document.body.appendChild(this.element),this.connected))}async show(e,t={}){const s=++this.showRequest;return!(!await this.ready||this.disposed||s!==this.showRequest)&&this.controller.show(e,t)}hide(){this.showRequest+=1,this.controller?.close()}dispose(){this.disposed=!0,this.hide(),this.resolveConnected(!1),this.element?.removeEventListener("hellotext--alert:connected",this.onConnect),this.element?.remove()}}class dt{static async load(e){const t=new dt({id:e,html:await I.webchats.get(e)});return t.rendered=t.render(),t}constructor(e){this.data=e,this.mounted=!1,this.rendered=Promise.resolve(!1)}async render(){return this.applyBehaviourOverride(),await this.stylesheetLoaded?(this.containerToAppendTo.appendChild(this.data.html),this.markCoexistingWidgets(),this.mounted=!0,!0):(console.warn("Hellotext webchat was not mounted because its stylesheet failed to load."),!1)}applyBehaviourOverride(){f.webchat.hasBehaviourOverride&&f.webchat.behaviour&&this.data.html.setAttribute("data-hellotext--webchat-behaviour-value",JSON.stringify(this.serializedBehaviour))}get serializedBehaviour(){const e=f.webchat.behaviour;return{trigger:this.serializeTrigger(e.trigger),delay_seconds:e.delaySeconds,first_visit_only:e.firstVisitOnly,once_per_session:e.oncePerSession}}serializeTrigger(e){return"onLoad"===e?"on_load":"onClick"===e?"on_click":e}get containerToAppendTo(){return document.querySelector(f.webchat.container)}get stylesheetLoaded(){return _.waitForStylesheet(_.latestStylesheet)}markCoexistingWidgets(){const e=this.data.html,t=document.querySelector(".hellotext--whatsapp-widget");e&&t&&(e.classList.add("hellotext--with-whatsapp-widget"),t.classList.add("hellotext--with-webchat"))}}class pt{static async load(e){const t=new pt({id:e,html:await I.whatsappWidgets.get(e)});return t.rendered=t.render(),t}constructor(e){this.data=e,this.mounted=!1,this.rendered=Promise.resolve(!1)}async render(){if(!this.data.html)return!1;const e=this.containerToAppendTo;return e?await this.stylesheetLoaded?(e.appendChild(this.data.html),this.markCoexistingWidgets(),this.mounted=!0,!0):(console.warn("Hellotext WhatsApp widget was not mounted because its stylesheet failed to load."),!1):(console.warn(`Hellotext WhatsApp widget was not mounted because the container ${f.whatsapp.container} was not found.`),!1)}get containerToAppendTo(){try{return document.querySelector(f.whatsapp.container)}catch(e){return null}}get stylesheetLoaded(){return _.waitForStylesheet(_.latestStylesheet)}markCoexistingWidgets(){const e=document.querySelector(".hellotext--webchat:not(.hellotext--whatsapp-widget)"),t=this.data.html;e&&t&&(e.classList.add("hellotext--with-whatsapp-widget"),t.classList.add("hellotext--with-webchat"))}}class mt{static async load(e,t={}){const s=new mt({id:e,html:await I.popups.get(e)},t);return s.rendered=s.render(),s}constructor(e,{container:t=f.popup.container,shouldMount:s=()=>!0}={}){this.data=e,this.container=t,this.mounted=!1,this.rendered=Promise.resolve(!1),this.shouldMount=s}async render(){if(!this.data.html||!this.shouldMount())return!1;const e=this.containerToAppendTo;return e?!!this.shouldMount()&&(e.appendChild(this.data.html),this.mounted=!0,this.shouldMount()||this.unmount(),this.mounted):(console.warn(`Hellotext popup was not mounted because the container ${this.container} was not found.`),!1)}unmount(){this.data.html?.remove(),this.mounted=!1}get containerToAppendTo(){try{return document.querySelector(this.container)}catch(e){return null}}}class gt{static get id(){return N.get("hello_user_id")}static get source(){return N.get("hello_user_source")}static get fingerprint(){return N.get("hello_user_identification_hash")}static remember(e,t,s){t&&N.set("hello_user_source",t),s&&N.set("hello_user_identification_hash",s),N.set("hello_user_id",e)}static forget(){N.delete("hello_user_id"),N.delete("hello_user_source"),N.delete("hello_user_identification_hash")}static get identificationData(){return this.id?{id:this.id,source:this.source}:{}}}function ft(e){if(null!=e){if("string"==typeof e){const t=e.trim();return""===t?void 0:t}if(Array.isArray(e))return e.map(e=>ft(e)).filter(e=>void 0!==e);if(e instanceof Date)return e.toISOString();if("object"==typeof e){const t=Object.keys(e).sort((e,t)=>e.localeCompare(t)).reduce((t,s)=>{const i=ft(e[s]);return void 0!==i&&(t[s]=i),t},{});return Object.keys(t).length>0?t:void 0}return"number"==typeof e||"boolean"==typeof e?e:void 0}}function bt(e,t,s={}){const i=ft({session:e,user_id:t,...s})||{};return JSON.stringify(i)}class yt{static matches(e,t){return!!e&&e===t}static async generate(e,t,s={}){return await async function(e){if(!globalThis.crypto?.subtle||"undefined"==typeof TextEncoder)return function(e){let t=5381;for(let s=0;s>>0).toString(16)}`}(e);const t=await globalThis.crypto.subtle.digest("SHA-256",(new TextEncoder).encode(e));return`v1:${Array.from(new Uint8Array(t)).map(e=>e.toString(16).padStart(2,"0")).join("")}`}(bt(e,t,s))}}class vt{static eventEmitter=new r;static forms;static business;static popup;static webchat;static whatsapp;static push;static alert;static initializationVersion=0;static async initialize(e,t={}){const s=++this.initializationVersion;this.popup?.unmount?.(),this.popup=void 0,this.alert?.dispose(),this.alert=null,this.push?.dispose(),this.push=null;const i=new _(e);this.business=i,this.page=new F,f.assign({push:{},...t}),R.initialize(this.page),this.forms=new lt,this.query=new y;const n=await i.hydrate();if(this.business!==i)return;!1!==t.push&&n?.push?.public_key&&ht.supported&&(this.push=new ht(n.push),this.push.initialize().catch(e=>{console.warn("Hellotext Push initialization failed:",e)}),n.alert?.html&&(this.alert=new ut(n.alert,i,this.push)));const r=!1!==t.popup&&this.deepMergePlainObjects(n&&n.popup||{},t.popup||{}),o=!1!==t.webchat&&this.mergeWebchatConfig(n&&n.webchat||{},t.webchat||{}),a=!1!==t.whatsappWidget&&this.mergeWhatsAppConfig(n&&n.whatsapp||{},t.whatsappWidget||{}),c=t.webchat&&!1!==t.webchat&&Object.prototype.hasOwnProperty.call(t.webchat,"behaviour");f.webchat.behaviourOverride=c;const l=[];if(o&&o.id&&(f.webchat.assign(o),l.push(dt.load(o.id).then(e=>{this.business===i&&(this.webchat=e)}))),a&&a.id&&(f.whatsapp.assign(a),l.push(pt.load(a.id).then(e=>{this.business===i&&(this.whatsapp=e)}))),r&&r.id){const e={container:"body",device:"auto",...r};f.popup.assign(e),l.push(mt.load(e.id,{container:e.container,shouldMount:()=>this.business===i&&this.initializationVersion===s}).then(e=>{this.business===i&&this.initializationVersion===s&&(this.popup=e)}))}await Promise.all(l),this.business===i&&this.initializationVersion===s&&"undefined"!=typeof MutationObserver&&this.forms.collectExistingFormsOnPage()}static mergeWebchatConfig(e,t){return this.deepMergePlainObjects(e,t)}static mergeWhatsAppConfig(e,t){return this.deepMergePlainObjects(e,t)}static deepMergePlainObjects(e,t){const s={...e};return Object.entries(t).forEach(([e,t])=>{this.isPlainObject(t)&&this.isPlainObject(s[e])?s[e]=this.deepMergePlainObjects(s[e],t):s[e]=t}),s}static isPlainObject(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}static async track(e,t={}){if(this.notInitialized)throw new ct;const s={...t&&t.headers||{},...this.headers},i={...gt.identificationData,...t.user_parameters||{}},n=t&&t.url?new F(t.url):this.page,r={session:this.session,user_parameters:i,action:e,...t,...n.trackingData};return delete r.headers,await I.events.create({headers:s,body:r,keepalive:k(r)})}static async identify(e,t={}){const s=await yt.generate(this.session,e,t);if(yt.matches(gt.fingerprint,s))return new v(!0,{json:async()=>({already_identified:!0})});const i=await I.identifications.create({user_id:e,...t});return i.succeeded&>.remember(e,t.source,s),i}static forget(){gt.forget()}static on(e,t){this.eventEmitter.addSubscriber(e,t)}static removeEventListener(e,t){this.eventEmitter.removeSubscriber(e,t)}static get session(){return R.session}static get isInitialized(){return void 0!==R.session}static get notInitialized(){return!this.business||void 0===this.business.id}static get headers(){if(this.notInitialized)throw new ct;return{Authorization:`Bearer ${this.business.id}`,Accept:"application/json","Content-Type":"application/json"}}}const wt=vt,Tt=new Map,St=class extends i.xI{static values={sections:Array};static targets=["title","description","primaryAction","secondaryAction"];initialize(){this.showRequest=0,this.submitting=!1,this.onStorage=this.onStorage.bind(this)}connect(){this.dispatch("connected",{detail:{controller:this}}),window.addEventListener("storage",this.onStorage)}disconnect(){this.close(),window.removeEventListener("storage",this.onStorage)}onStorage(e){e.key===this.storageKey&&this.dismissal.dismissedUntil>Date.now()&&this.close()}async show(e,{force:t=!1,title:s,description:i,primaryAction:n,secondaryAction:r}={}){const o=++this.showRequest,a=this.sectionsValue.find(t=>t.kind===e);return a?(await(this.alert.push.ready?.catch(()=>{})),!(o!==this.showRequest||this.alert.disposed||!this.element.isConnected||(this.unavailable||!t&&this.dismissal.dismissedUntil>Date.now()?(this.close(),1):(this.titleTarget.textContent=s??a.title,this.descriptionTarget.textContent=i??a.description,this.primaryActionTarget.textContent=n??a.primary_action,this.secondaryActionTarget.textContent=r??a.secondary_action,this.kind=e,this.page=wt.page.trackingData.page,this.element.hidden=!1,this.record("shown"),wt.eventEmitter.dispatch("alert:shown",{kind:e}),0)))):(this.close(),!1)}hide(){if(this.element.hidden||this.submitting)return;const e=this.dismissal.dismissals+1,t={dismissals:e,dismissedUntil:Date.now()+864e5*(1===e?7:30)};Tt.set(this.storageKey,t);try{localStorage.setItem(this.storageKey,JSON.stringify(t))}catch(e){}this.close(),this.record("dismissed"),wt.eventEmitter.dispatch("alert:dismissed",{kind:this.kind})}close(){this.showRequest+=1,this.element.hidden=!0}async subscribe(){if(!this.submitting&&!this.element.hidden){if(this.unavailable)return this.close();this.submitting=!0,this.primaryActionTarget.disabled=!0,this.secondaryActionTarget.disabled=!0,this.element.setAttribute("aria-busy","true");try{this.record("accepted"),wt.eventEmitter.dispatch("alert:accepted",{kind:this.kind});const e=await this.alert.push.subscribe();return this.unavailable&&this.close(),e?.failed&&this.dispatch("error",{detail:{response:e}}),e}catch(e){this.unavailable&&this.close(),this.dispatch("error",{detail:{error:e}})}finally{this.submitting=!1,this.primaryActionTarget.disabled=!1,this.secondaryActionTarget.disabled=!1,this.element.removeAttribute("aria-busy")}}}async record(e){try{const t=await I.pushAlerts.create({section:this.kind,kind:e,page:this.page});t.failed&&console.warn("Hellotext Smart Alert submission failed:",t)}catch(e){console.warn("Hellotext Smart Alert submission failed:",e)}}get unavailable(){return this.alert.push.disposed||this.alert.push.subscribed||"undefined"==typeof Notification||"denied"===Notification.permission}get storageKey(){return`hellotext:alert:${this.alert.business.id}`}get dismissal(){try{const e=JSON.parse(localStorage.getItem(this.storageKey));Number.isSafeInteger(e?.dismissals)&&e.dismissals>0&&Number.isFinite(e.dismissedUntil)&&e.dismissedUntil>=0&&Tt.set(this.storageKey,e)}catch(e){}return Tt.get(this.storageKey)||{dismissals:0,dismissedUntil:0}}},Ct=class extends i.xI{static values={data:Object,step:{type:Number,default:1}};static targets=["inputContainer","input","button","otpContainer"];initialize(){this.form=new at(this.dataValue,this.element)}connect(){super.connect(),this.element.addEventListener("submit",this.submit.bind(this)),"INPUT"!==document.activeElement.tagName&&this.inputTargets[0].focus()}async submit(e){if(e.preventDefault(),this.invalid)return this.showErrorMessages();this.clearErrorMessages(),this.formData=Object.fromEntries(new FormData(this.element)),this.buttonTarget.disabled=!0;const t=await T.submit(this.form.id,this.formData);this.buttonTarget.disabled=!1;const s=await t.json();if(t.failed)return s.errors.forEach(e=>{const{type:t,parameter:s}=e,i=this.inputTargets.find(e=>e.name===s);i.setCustomValidity(wt.business.locale.errors[t]),i.reportValidity(),i.addEventListener("input",()=>{i.setCustomValidity(""),i.reportValidity()})}),this.showErrorMessages();this.buttonTarget.style.display="none",this.element.querySelectorAll("input").forEach(e=>e.disabled=!0),this.completed()}completed(){if(this.form.markAsCompleted(this.formData),!f.forms.shouldShowSuccessMessage)return this.element.remove();"string"==typeof f.forms.successMessage?this.element.innerHTML=f.forms.successMessage:this.element.innerHTML=wt.business.locale.forms[this.form.localeAuthKey]}showErrorMessages(){this.inputTargets.forEach(e=>{const t=e.closest("article").querySelector("[data-error-container]");e.validity.valid?t.innerText="":t.innerText=e.validationMessage})}clearErrorMessages(){this.inputTargets.forEach(e=>{e.setCustomValidity(""),e.closest("article").querySelector("[data-error-container]").innerText=""})}inputTargetConnected(e){e.getAttribute("data-default-value")&&(e.value=e.getAttribute("data-default-value"))}get requiredInputs(){return this.inputTargets.filter(e=>e.required)}get invalid(){return!this.element.checkValidity()}},Ot=class extends i.xI{static values={fadeDistance:{type:Number,default:64},id:String,kind:String,pageStartOffset:{type:Number,default:0},utm:Object};static targets=["carouselContainer","leftFade","rightFade","carouselCard"];connect(){this.updateFades(),this.observeContainerSize()}disconnect(){this.resizeObserver?.disconnect()}setId({detail:e}){this.idValue=e,this.element.id=e}onScroll(){this.updateFades()}quickReply({currentTarget:e}){const t=e.closest('[data-hellotext--message-target="carouselCard"]'),s=e.closest('[data-controller~="hellotext--message"]'),i=e.dataset.text||e.textContent.trim();this.dispatch("quickReply",{detail:{id:this.idValue,product:t?.dataset.id,buttonId:e.dataset.id,body:i,cardElement:t||s||e}})}addToCart({currentTarget:e}){const t=e.closest('[data-hellotext--message-target="carouselCard"]'),{id:s,reference:i,source:n}=t.dataset,r={product:s,quantity:1};this.hasUtmValue&&wt.page.utm.save(this.utmValue),wt.eventEmitter.dispatch("cart.added",{object_parameters:{items:[{...r,...i&&{reference:i},...n&&{source:n}}]},source:{kind:this.kindValue,message_id:this.idValue,button_id:e.dataset.id}})}moveToLeft(){if(!this.hasCarouselContainerTarget)return;const e=this.getPreviousPageScrollLeft(),t=this.carouselContainerTarget.scrollLeft-e;t<1||this.carouselContainerTarget.scrollBy({left:-t,behavior:"smooth"})}moveToRight(){if(!this.hasCarouselContainerTarget)return;const e=this.getNextPageScrollLeft()-this.carouselContainerTarget.scrollLeft;e<1||this.carouselContainerTarget.scrollBy({left:e,behavior:"smooth"})}getScrollAmount(){return this.getCardScrollAmount()}getCardScrollAmount(){const e=this.carouselContainerTarget.querySelector(".message__carousel_card");return e?e.offsetWidth+this.getGap():280}getPageScrollAmount(){const e=this.carouselContainerTarget.clientWidth-this.getGap();return e>0?e:this.getCardScrollAmount()}getNextPageScrollLeft(){const e=this.getCurrentScrollLeft(),t=e+this.carouselContainerTarget.clientWidth,s=this.getCardMetrics().find(e=>e.end>t+1),i=s?this.getPageAlignedScrollLeft(s.start):e+this.getPageScrollAmount(),n=e+this.getPageScrollAmount();return this.clampScrollLeft(i>e+1?i:n)}getPreviousPageScrollLeft(){const e=this.getCurrentScrollLeft();if(e<=1)return 0;const t=Math.max(e-this.getPageScrollAmount(),0);if(t<=1)return 0;const s=this.getCardMetrics(),i=s.find(s=>s.start>=t-1&&s.startt.start{const t=this.getCardScrollLeft(e);return{start:t,end:t+e.offsetWidth}})}getCardScrollLeft(e){const t=e.getBoundingClientRect(),s=this.carouselContainerTarget.getBoundingClientRect();return t.left||t.width||s.left||s.width?t.left-s.left+this.carouselContainerTarget.scrollLeft:e.offsetLeft||0}getCurrentScrollLeft(){return this.clampScrollLeft(this.carouselContainerTarget.scrollLeft)}clampScrollLeft(e){const t=Math.max(this.carouselContainerTarget.scrollWidth-this.carouselContainerTarget.clientWidth,0);return Math.min(Math.max(e,0),t)}getGap(){const e=window.getComputedStyle(this.carouselContainerTarget),t=Number.parseFloat(e.columnGap||e.gap);return Number.isFinite(t)?t:16}getFadeDistance(){return Number.isFinite(this.fadeDistanceValue)?this.fadeDistanceValue:64}getPageStartOffset(){return Number.isFinite(this.pageStartOffsetValue)?this.pageStartOffsetValue:0}observeContainerSize(){this.hasCarouselContainerTarget&&window.ResizeObserver&&(this.resizeObserver=new ResizeObserver(()=>this.updateFades()),this.resizeObserver.observe(this.carouselContainerTarget))}updateFades(){if(!this.hasCarouselContainerTarget)return;const e=Math.max(this.carouselContainerTarget.scrollWidth-this.carouselContainerTarget.clientWidth,0);if(e<=1)return this.hideFade(this.leftFadeTarget),void this.hideFade(this.rightFadeTarget);const t=Math.min(Math.max(this.carouselContainerTarget.scrollLeft,0),e),s=this.getFadeDistance();this.setFadeOpacity(this.leftFadeTarget,t/s),this.setFadeOpacity(this.rightFadeTarget,(e-t)/s)}setFadeOpacity(e,t){const s=Math.min(Math.max(t,0),1);s<=.05?this.hideFade(e):(e.classList.remove("hidden"),e.removeAttribute("disabled"),e.removeAttribute("tabindex"),e.setAttribute("aria-hidden","false"),e.style.opacity=s.toFixed(3),e.style.pointerEvents="auto")}hideFade(e){e.style.opacity="0",e.style.pointerEvents="none",e.setAttribute("aria-hidden","true"),e.setAttribute("tabindex","-1"),"disabled"in e&&(e.disabled=!0),e.classList.add("hidden")}},Et=class extends i.xI{static targets=["bubble","dialog","step","completed","input","submitButton","globalError","resendButton","changeDestinationButton","deliveryCopy","noDeliveryCopy"];static values={capture:Object,device:String,hasBubble:Boolean,id:String};initialize(){this.stepIndex=0,this.resendLabel=this.hasResendButtonTarget?this.resendButtonTarget.textContent.trim():""}connect(){wt.eventEmitter.dispatch("popup:mounted"),this.evaluateDisplay()}disconnect(){this.stopResendCooldown()}open(e){e&&e.preventDefault(),this.hasBubbleTarget&&(this.bubbleTarget.hidden=!0),this.dialogTarget.hidden=!1,wt.eventEmitter.dispatch("popup:opened")}close(e){e&&e.preventDefault(),this.dismissed=!0,this.dialogTarget.hidden=!0,this.hasBubbleTarget&&(this.bubbleTarget.hidden=!0),this.element.hidden=!0,wt.eventEmitter.dispatch("popup:closed")}async next(e){e&&e.preventDefault(),this.clearCustomValidity(),this.currentStepValid()?(this.clearErrorMessages(this.currentStepInputs),this.stepIndex{e.disabled=!0});try{const e=this.submissionPayload(),t=await C.submit(this.idValue,e,this.idempotencyKeyFor(e));if(t.failed)return void await this.handleSubmissionError(t);const s=await t.json();this.submissionId=s.id,this.submissionVerificationState=s.verification_state,this.submissionActionToken=s.action_token,this.submissionDeliveryStatus=s.delivery_status,this.submissionDeliveryChannel=s.delivery_channel,this.submissionDestination=s.destination,this.resetSubmissionRequest()}catch(e){return void this.showGlobalError()}finally{this.submitButtonTargets.forEach(e=>{e.disabled=!1})}this.showCompleted()}else this.showErrorMessages(this.currentStepInputs)}evaluateDisplay(){!this.dismissed&&this.matchesDevice()?this.showInitialState():this.element.hidden=!0}showInitialState(){if(this.element.hidden=!1,this.hasBubbleValue&&this.hasBubbleTarget)return this.bubbleTarget.hidden=!1,void(this.dialogTarget.hidden=!0);this.hasBubbleTarget&&(this.bubbleTarget.hidden=!0),this.dialogTarget.hidden=!1,wt.eventEmitter.dispatch("popup:opened")}showStep(e){this.stepIndex=e,this.stepTargets.forEach((t,s)=>{t.hidden=s!==e}),this.completedTarget.hidden=!0}showCompleted(){this.stepTargets.forEach(e=>{e.hidden=!0}),this.interpolateCompletionCopy(),this.configureCompletionActions(),this.completedTarget.hidden=!1}interpolateCompletionCopy(){const e=this.completedIdentity;if(!e)return;const t={destination:e.value,channel:this.submissionDeliveryChannel||e.kind};this.completionTextTemplates.forEach(({node:e,template:s})=>{e.nodeValue=s.replace(/\{(destination|channel)\}/g,(e,s)=>t[s]||e)})}identityValue(e){const t=this.inputValue(e).trim();if("phone"!==e.dataset.popupFieldKind||t.startsWith("+"))return t;const s=e.dataset.popupPhonePrefix;return s?`${s}${t.replace(/^0+/,"")}`:t}configureCompletionActions(){const e="not_required"!==this.submissionDeliveryStatus;if(this.revealCompletionCopy(e),!e)return void this.completedTarget.querySelector("[data-delivery-actions]")?.setAttribute("hidden","");const t=this.completedIdentity;t&&(this.hasChangeDestinationButtonTarget&&(this.changeDestinationButtonTarget.textContent=this.changeDestinationButtonTarget.dataset[`${t.kind}Label`],this.changeDestinationButtonTarget.hidden=!1),this.submissionId&&this.submissionActionToken&&"queued"===this.submissionDeliveryStatus&&"unverified"===this.submissionVerificationState&&this.hasResendButtonTarget&&(this.resendButtonTarget.hidden=!1,this.startResendCooldown(60)))}async resend(e){if(e&&e.preventDefault(),this.submissionId&&this.submissionActionToken&&!this.resendPending&&!this.resendCooldownActive&&this.completedIdentity){this.resendPending=!0,this.resendButtonTarget.disabled=!0;try{const e=await C.resend(this.idValue,this.submissionId,this.submissionActionToken),t=Number(e.data.headers?.get("Retry-After"))||60;e.succeeded||429===e.data.status?this.startResendCooldown(t):this.resendButtonTarget.disabled=!1}catch(e){this.resendButtonTarget.disabled=!1}finally{this.resendPending=!1}}}async changeDestination(e){if(e&&e.preventDefault(),!this.submissionId||!this.submissionActionToken||this.changeDestinationPending)return;const t=this.completedIdentity?.input;if(!t)return;const s=this.stepTargets.findIndex(e=>e.dataset.stepId===t.dataset.popupStepId);if(!(s<0)){this.changeDestinationPending=!0,this.changeDestinationButtonTarget.disabled=!0;try{if((await C.cancel(this.idValue,this.submissionId,this.submissionActionToken)).failed)return;this.stopResendCooldown(),this.submissionId=null,this.submissionActionToken=null,this.submissionVerificationState=null,this.submissionDeliveryStatus=null,this.submissionDeliveryChannel=null,this.submissionDestination=null,this.resetSubmissionRequest(),this.showStep(s),t.focus()}catch(e){}finally{this.changeDestinationPending=!1,this.changeDestinationButtonTarget.disabled=!1}}}startResendCooldown(e){this.stopResendCooldown(),this.resendCooldownEndsAt=Date.now()+1e3*Math.max(e,1),this.updateResendCountdown(),this.resendTimer=window.setInterval(()=>this.updateResendCountdown(),1e3)}stopResendCooldown(){this.resendTimer&&window.clearInterval(this.resendTimer),this.resendTimer=null,this.resendCooldownEndsAt=null}updateResendCountdown(){const e=Math.max(0,Math.ceil((this.resendCooldownEndsAt-Date.now())/1e3));if(0===e)return this.stopResendCooldown(),this.resendButtonTarget.textContent=this.resendLabel,void(this.resendButtonTarget.disabled=!1);const t=`${Math.floor(e/60)}:${String(e%60).padStart(2,"0")}`,s=this.resendButtonTarget.dataset.countdownLabel||`${this.resendLabel} %{time}`;this.resendButtonTarget.textContent=s.replace("%{time}",t),this.resendButtonTarget.disabled=!0}get resendCooldownActive(){return this.resendCooldownEndsAt>Date.now()}get completionIdentity(){return this.identityInputs.map(e=>({input:e,kind:e.dataset.popupFieldKind,value:this.identityValue(e)})).find(({value:e})=>e)}get completedIdentity(){if(this.submissionDestination&&this.submissionDeliveryChannel){const e="email"===this.submissionDeliveryChannel?"email":"phone";return{input:this.identityInputs.find(t=>t.dataset.popupFieldKind===e),kind:e,value:this.submissionDestination}}return this.completionIdentity}revealCompletionCopy(e){this.hasDeliveryCopyTarget&&this.deliveryCopyTargets.forEach(t=>{t.hidden=!e}),this.hasNoDeliveryCopyTarget&&this.noDeliveryCopyTargets.forEach(t=>{t.hidden=e})}currentStepValid(){return this.currentStepInputs.every(e=>e.checkValidity())}showErrorMessages(e){e.forEach(e=>{const t=e.closest(".hellotext--popup-field")?.querySelector("[data-error-container]");t&&(t.textContent=e.validity.valid?"":e.validationMessage)})}clearErrorMessages(e=this.inputTargets){e.forEach(e=>{const t=e.closest(".hellotext--popup-field")?.querySelector("[data-error-container]");t&&(t.textContent="")})}clearCustomValidity(){this.inputTargets.forEach(e=>e.setCustomValidity(""))}clearGlobalError(){this.hasGlobalErrorTarget&&(this.globalErrorTarget.textContent="",this.globalErrorTarget.hidden=!0)}showGlobalError(e=null){this.hasGlobalErrorTarget&&(this.globalErrorTarget.textContent=e||this.globalErrorTarget.dataset.submitError||"Unable to submit. Please try again.",this.globalErrorTarget.hidden=!1)}async handleSubmissionError(e){let t;try{t=await e.json()}catch(e){return void this.showGlobalError()}const s=t.errors||[],i=[];s.forEach(e=>{const t=this.inputForError(e);t?(t.setCustomValidity(e.description||t.validationMessage),t.reportValidity()):e.description&&i.push(e.description)}),this.showErrorMessages(this.inputTargets),i.length?this.showGlobalError(i.join(" ")):s.length||this.showGlobalError()}inputForError(e){const t=e.parameter;return t?this.inputTargets.find(e=>e.dataset.popupFieldKind===t||e.dataset.popupFieldKey===t):null}submissionPayload(){const e={metadata:{capture:this.captureValue||{},fields:{},steps:[]}};return this.stepTargets.forEach(t=>{const s={};this.inputsForStep(t).forEach(t=>{const i=this.inputValue(t),n=t.dataset.popupFieldKey||t.name;s[n]=i,e.metadata.fields[n]=i,"email"===t.dataset.popupFieldKind&&(e.email=i),"phone"===t.dataset.popupFieldKind&&(e.phone=i)}),e.metadata.steps.push({id:t.dataset.stepId,name:t.dataset.stepName,fields:s})}),e}idempotencyKeyFor(e){const t=JSON.stringify(e);return this.submissionPayloadSnapshot!==t&&(this.submissionPayloadSnapshot=t,this.submissionIdempotencyKey=C.idempotencyKey()),this.submissionIdempotencyKey}resetSubmissionRequest(){this.submissionPayloadSnapshot=null,this.submissionIdempotencyKey=null}inputValue(e){return"checkbox"===e.type?e.checked:e.value}inputsForStep(e){return this.inputTargets.filter(t=>t.dataset.popupStepId===e.dataset.stepId)}get identityInputs(){const e=this.inputTargets.filter(e=>["email","phone"].includes(e.dataset.popupFieldKind));return e.filter(e=>e.required).concat(e.filter(e=>!e.required))}get completionTextTemplates(){if(this._completionTextTemplates)return this._completionTextTemplates;const e=document.createTreeWalker(this.completedTarget,NodeFilter.SHOW_TEXT);for(this._completionTextTemplates=[];e.nextNode();)this._completionTextTemplates.push({node:e.currentNode,template:e.currentNode.nodeValue});return this._completionTextTemplates}matchesDevice(){return"all"===this.deviceValue||("mobile"===this.deviceValue?window.innerWidth<768:"desktop"!==this.deviceValue||window.innerWidth>=768)}get currentStep(){return this.stepTargets[this.stepIndex]}get currentStepInputs(){return this.inputsForStep(this.currentStep)}},At=["start","end"],xt=["top","right","bottom","left"].reduce((e,t)=>e.concat(t,t+"-"+At[0],t+"-"+At[1]),[]),Mt=Math.min,kt=Math.max,It=Math.round,Lt=Math.floor,Pt=e=>({x:e,y:e}),_t={left:"right",right:"left",bottom:"top",top:"bottom"},Nt={start:"end",end:"start"};function Dt(e,t,s){return kt(e,Mt(t,s))}function Ft(e,t){return"function"==typeof e?e(t):e}function Rt(e){return e.split("-")[0]}function Bt(e){return e.split("-")[1]}function $t(e){return"x"===e?"y":"x"}function jt(e){return"y"===e?"height":"width"}const Vt=new Set(["top","bottom"]);function qt(e){return Vt.has(Rt(e))?"y":"x"}function Ut(e){return $t(qt(e))}function zt(e,t,s){void 0===s&&(s=!1);const i=Bt(e),n=Ut(e),r=jt(n);let o="x"===n?i===(s?"end":"start")?"right":"left":"start"===i?"bottom":"top";return t.reference[r]>t.floating[r]&&(o=Yt(o)),[o,Yt(o)]}function Wt(e){return e.replace(/start|end/g,e=>Nt[e])}const Ht=["left","right"],Kt=["right","left"],Gt=["top","bottom"],Jt=["bottom","top"];function Yt(e){return e.replace(/left|right|bottom|top/g,e=>_t[e])}function Zt(e){const{x:t,y:s,width:i,height:n}=e;return{width:i,height:n,top:s,left:t,right:t+i,bottom:s+n,x:t,y:s}}function Xt(e,t,s){let{reference:i,floating:n}=e;const r=qt(t),o=Ut(t),a=jt(o),c=Rt(t),l="y"===r,h=i.x+i.width/2-n.width/2,u=i.y+i.height/2-n.height/2,d=i[a]/2-n[a]/2;let p;switch(c){case"top":p={x:h,y:i.y-n.height};break;case"bottom":p={x:h,y:i.y+i.height};break;case"right":p={x:i.x+i.width,y:u};break;case"left":p={x:i.x-n.width,y:u};break;default:p={x:i.x,y:i.y}}switch(Bt(t)){case"start":p[o]-=d*(s&&l?-1:1);break;case"end":p[o]+=d*(s&&l?-1:1)}return p}async function Qt(e,t){var s;void 0===t&&(t={});const{x:i,y:n,platform:r,rects:o,elements:a,strategy:c}=e,{boundary:l="clippingAncestors",rootBoundary:h="viewport",elementContext:u="floating",altBoundary:d=!1,padding:p=0}=Ft(t,e),m=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}}(p),g=a[d?"floating"===u?"reference":"floating":u],f=Zt(await r.getClippingRect({element:null==(s=await(null==r.isElement?void 0:r.isElement(g)))||s?g:g.contextElement||await(null==r.getDocumentElement?void 0:r.getDocumentElement(a.floating)),boundary:l,rootBoundary:h,strategy:c})),b="floating"===u?{x:i,y:n,width:o.floating.width,height:o.floating.height}:o.reference,y=await(null==r.getOffsetParent?void 0:r.getOffsetParent(a.floating)),v=await(null==r.isElement?void 0:r.isElement(y))&&await(null==r.getScale?void 0:r.getScale(y))||{x:1,y:1},w=Zt(r.convertOffsetParentRelativeRectToViewportRelativeRect?await r.convertOffsetParentRelativeRectToViewportRelativeRect({elements:a,rect:b,offsetParent:y,strategy:c}):b);return{top:(f.top-w.top+m.top)/v.y,bottom:(w.bottom-f.bottom+m.bottom)/v.y,left:(f.left-w.left+m.left)/v.x,right:(w.right-f.right+m.right)/v.x}}const es=new Set(["left","top"]);function ts(){return"undefined"!=typeof window}function ss(e){return rs(e)?(e.nodeName||"").toLowerCase():"#document"}function is(e){var t;return(null==e||null==(t=e.ownerDocument)?void 0:t.defaultView)||window}function ns(e){var t;return null==(t=(rs(e)?e.ownerDocument:e.document)||window.document)?void 0:t.documentElement}function rs(e){return!!ts()&&(e instanceof Node||e instanceof is(e).Node)}function os(e){return!!ts()&&(e instanceof Element||e instanceof is(e).Element)}function as(e){return!!ts()&&(e instanceof HTMLElement||e instanceof is(e).HTMLElement)}function cs(e){return!(!ts()||"undefined"==typeof ShadowRoot)&&(e instanceof ShadowRoot||e instanceof is(e).ShadowRoot)}const ls=new Set(["inline","contents"]);function hs(e){const{overflow:t,overflowX:s,overflowY:i,display:n}=Ss(e);return/auto|scroll|overlay|hidden|clip/.test(t+i+s)&&!ls.has(n)}const us=new Set(["table","td","th"]);function ds(e){return us.has(ss(e))}const ps=[":popover-open",":modal"];function ms(e){return ps.some(t=>{try{return e.matches(t)}catch(e){return!1}})}const gs=["transform","translate","scale","rotate","perspective"],fs=["transform","translate","scale","rotate","perspective","filter"],bs=["paint","layout","strict","content"];function ys(e){const t=vs(),s=os(e)?Ss(e):e;return gs.some(e=>!!s[e]&&"none"!==s[e])||!!s.containerType&&"normal"!==s.containerType||!t&&!!s.backdropFilter&&"none"!==s.backdropFilter||!t&&!!s.filter&&"none"!==s.filter||fs.some(e=>(s.willChange||"").includes(e))||bs.some(e=>(s.contain||"").includes(e))}function vs(){return!("undefined"==typeof CSS||!CSS.supports)&&CSS.supports("-webkit-backdrop-filter","none")}const ws=new Set(["html","body","#document"]);function Ts(e){return ws.has(ss(e))}function Ss(e){return is(e).getComputedStyle(e)}function Cs(e){return os(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function Os(e){if("html"===ss(e))return e;const t=e.assignedSlot||e.parentNode||cs(e)&&e.host||ns(e);return cs(t)?t.host:t}function Es(e){const t=Os(e);return Ts(t)?e.ownerDocument?e.ownerDocument.body:e.body:as(t)&&hs(t)?t:Es(t)}function As(e,t,s){var i;void 0===t&&(t=[]),void 0===s&&(s=!0);const n=Es(e),r=n===(null==(i=e.ownerDocument)?void 0:i.body),o=is(n);if(r){const e=xs(o);return t.concat(o,o.visualViewport||[],hs(n)?n:[],e&&s?As(e):[])}return t.concat(n,As(n,[],s))}function xs(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function Ms(e){const t=Ss(e);let s=parseFloat(t.width)||0,i=parseFloat(t.height)||0;const n=as(e),r=n?e.offsetWidth:s,o=n?e.offsetHeight:i,a=It(s)!==r||It(i)!==o;return a&&(s=r,i=o),{width:s,height:i,$:a}}function ks(e){return os(e)?e:e.contextElement}function Is(e){const t=ks(e);if(!as(t))return Pt(1);const s=t.getBoundingClientRect(),{width:i,height:n,$:r}=Ms(t);let o=(r?It(s.width):s.width)/i,a=(r?It(s.height):s.height)/n;return o&&Number.isFinite(o)||(o=1),a&&Number.isFinite(a)||(a=1),{x:o,y:a}}const Ls=Pt(0);function Ps(e){const t=is(e);return vs()&&t.visualViewport?{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}:Ls}function _s(e,t,s,i){void 0===t&&(t=!1),void 0===s&&(s=!1);const n=e.getBoundingClientRect(),r=ks(e);let o=Pt(1);t&&(i?os(i)&&(o=Is(i)):o=Is(e));const a=function(e,t,s){return void 0===t&&(t=!1),!(!s||t&&s!==is(e))&&t}(r,s,i)?Ps(r):Pt(0);let c=(n.left+a.x)/o.x,l=(n.top+a.y)/o.y,h=n.width/o.x,u=n.height/o.y;if(r){const e=is(r),t=i&&os(i)?is(i):i;let s=e,n=xs(s);for(;n&&i&&t!==s;){const e=Is(n),t=n.getBoundingClientRect(),i=Ss(n),r=t.left+(n.clientLeft+parseFloat(i.paddingLeft))*e.x,o=t.top+(n.clientTop+parseFloat(i.paddingTop))*e.y;c*=e.x,l*=e.y,h*=e.x,u*=e.y,c+=r,l+=o,s=is(n),n=xs(s)}}return Zt({width:h,height:u,x:c,y:l})}function Ns(e,t){const s=Cs(e).scrollLeft;return t?t.left+s:_s(ns(e)).left+s}function Ds(e,t,s){void 0===s&&(s=!1);const i=e.getBoundingClientRect();return{x:i.left+t.scrollLeft-(s?0:Ns(e,i)),y:i.top+t.scrollTop}}const Fs=new Set(["absolute","fixed"]);function Rs(e,t,s){let i;if("viewport"===t)i=function(e,t){const s=is(e),i=ns(e),n=s.visualViewport;let r=i.clientWidth,o=i.clientHeight,a=0,c=0;if(n){r=n.width,o=n.height;const e=vs();(!e||e&&"fixed"===t)&&(a=n.offsetLeft,c=n.offsetTop)}return{width:r,height:o,x:a,y:c}}(e,s);else if("document"===t)i=function(e){const t=ns(e),s=Cs(e),i=e.ownerDocument.body,n=kt(t.scrollWidth,t.clientWidth,i.scrollWidth,i.clientWidth),r=kt(t.scrollHeight,t.clientHeight,i.scrollHeight,i.clientHeight);let o=-s.scrollLeft+Ns(e);const a=-s.scrollTop;return"rtl"===Ss(i).direction&&(o+=kt(t.clientWidth,i.clientWidth)-n),{width:n,height:r,x:o,y:a}}(ns(e));else if(os(t))i=function(e,t){const s=_s(e,!0,"fixed"===t),i=s.top+e.clientTop,n=s.left+e.clientLeft,r=as(e)?Is(e):Pt(1);return{width:e.clientWidth*r.x,height:e.clientHeight*r.y,x:n*r.x,y:i*r.y}}(t,s);else{const s=Ps(e);i={x:t.x-s.x,y:t.y-s.y,width:t.width,height:t.height}}return Zt(i)}function Bs(e,t){const s=Os(e);return!(s===t||!os(s)||Ts(s))&&("fixed"===Ss(s).position||Bs(s,t))}function $s(e,t,s){const i=as(t),n=ns(t),r="fixed"===s,o=_s(e,!0,r,t);let a={scrollLeft:0,scrollTop:0};const c=Pt(0);function l(){c.x=Ns(n)}if(i||!i&&!r)if(("body"!==ss(t)||hs(n))&&(a=Cs(t)),i){const e=_s(t,!0,r,t);c.x=e.x+t.clientLeft,c.y=e.y+t.clientTop}else n&&l();r&&!i&&n&&l();const h=!n||i||r?Pt(0):Ds(n,a);return{x:o.left+a.scrollLeft-c.x-h.x,y:o.top+a.scrollTop-c.y-h.y,width:o.width,height:o.height}}function js(e){return"static"===Ss(e).position}function Vs(e,t){if(!as(e)||"fixed"===Ss(e).position)return null;if(t)return t(e);let s=e.offsetParent;return ns(e)===s&&(s=s.ownerDocument.body),s}function qs(e,t){const s=is(e);if(ms(e))return s;if(!as(e)){let t=Os(e);for(;t&&!Ts(t);){if(os(t)&&!js(t))return t;t=Os(t)}return s}let i=Vs(e,t);for(;i&&ds(i)&&js(i);)i=Vs(i,t);return i&&Ts(i)&&js(i)&&!ys(i)?s:i||function(e){let t=Os(e);for(;as(t)&&!Ts(t);){if(ys(t))return t;if(ms(t))return null;t=Os(t)}return null}(e)||s}const Us={convertOffsetParentRelativeRectToViewportRelativeRect:function(e){let{elements:t,rect:s,offsetParent:i,strategy:n}=e;const r="fixed"===n,o=ns(i),a=!!t&&ms(t.floating);if(i===o||a&&r)return s;let c={scrollLeft:0,scrollTop:0},l=Pt(1);const h=Pt(0),u=as(i);if((u||!u&&!r)&&(("body"!==ss(i)||hs(o))&&(c=Cs(i)),as(i))){const e=_s(i);l=Is(i),h.x=e.x+i.clientLeft,h.y=e.y+i.clientTop}const d=!o||u||r?Pt(0):Ds(o,c,!0);return{width:s.width*l.x,height:s.height*l.y,x:s.x*l.x-c.scrollLeft*l.x+h.x+d.x,y:s.y*l.y-c.scrollTop*l.y+h.y+d.y}},getDocumentElement:ns,getClippingRect:function(e){let{element:t,boundary:s,rootBoundary:i,strategy:n}=e;const r=[..."clippingAncestors"===s?ms(t)?[]:function(e,t){const s=t.get(e);if(s)return s;let i=As(e,[],!1).filter(e=>os(e)&&"body"!==ss(e)),n=null;const r="fixed"===Ss(e).position;let o=r?Os(e):e;for(;os(o)&&!Ts(o);){const t=Ss(o),s=ys(o);s||"fixed"!==t.position||(n=null),(r?!s&&!n:!s&&"static"===t.position&&n&&Fs.has(n.position)||hs(o)&&!s&&Bs(e,o))?i=i.filter(e=>e!==o):n=t,o=Os(o)}return t.set(e,i),i}(t,this._c):[].concat(s),i],o=r[0],a=r.reduce((e,s)=>{const i=Rs(t,s,n);return e.top=kt(i.top,e.top),e.right=Mt(i.right,e.right),e.bottom=Mt(i.bottom,e.bottom),e.left=kt(i.left,e.left),e},Rs(t,o,n));return{width:a.right-a.left,height:a.bottom-a.top,x:a.left,y:a.top}},getOffsetParent:qs,getElementRects:async function(e){const t=this.getOffsetParent||qs,s=this.getDimensions,i=await s(e.floating);return{reference:$s(e.reference,await t(e.floating),e.strategy),floating:{x:0,y:0,width:i.width,height:i.height}}},getClientRects:function(e){return Array.from(e.getClientRects())},getDimensions:function(e){const{width:t,height:s}=Ms(e);return{width:t,height:s}},getScale:Is,isElement:os,isRTL:function(e){return"rtl"===Ss(e).direction}};function zs(e,t){return e.x===t.x&&e.y===t.y&&e.width===t.width&&e.height===t.height}const Ws=function(e){return void 0===e&&(e=0),{name:"offset",options:e,async fn(t){var s,i;const{x:n,y:r,placement:o,middlewareData:a}=t,c=await async function(e,t){const{placement:s,platform:i,elements:n}=e,r=await(null==i.isRTL?void 0:i.isRTL(n.floating)),o=Rt(s),a=Bt(s),c="y"===qt(s),l=es.has(o)?-1:1,h=r&&c?-1:1,u=Ft(t,e);let{mainAxis:d,crossAxis:p,alignmentAxis:m}="number"==typeof u?{mainAxis:u,crossAxis:0,alignmentAxis:null}:{mainAxis:u.mainAxis||0,crossAxis:u.crossAxis||0,alignmentAxis:u.alignmentAxis};return a&&"number"==typeof m&&(p="end"===a?-1*m:m),c?{x:p*h,y:d*l}:{x:d*l,y:p*h}}(t,e);return o===(null==(s=a.offset)?void 0:s.placement)&&null!=(i=a.arrow)&&i.alignmentOffset?{}:{x:n+c.x,y:r+c.y,data:{...c,placement:o}}}}},Hs=function(e){return void 0===e&&(e={}),{name:"shift",options:e,async fn(t){const{x:s,y:i,placement:n}=t,{mainAxis:r=!0,crossAxis:o=!1,limiter:a={fn:e=>{let{x:t,y:s}=e;return{x:t,y:s}}},...c}=Ft(e,t),l={x:s,y:i},h=await Qt(t,c),u=qt(Rt(n)),d=$t(u);let p=l[d],m=l[u];if(r){const e="y"===d?"bottom":"right";p=Dt(p+h["y"===d?"top":"left"],p,p-h[e])}if(o){const e="y"===u?"bottom":"right";m=Dt(m+h["y"===u?"top":"left"],m,m-h[e])}const g=a.fn({...t,[d]:p,[u]:m});return{...g,data:{x:g.x-s,y:g.y-i,enabled:{[d]:r,[u]:o}}}}}},Ks=function(e){return void 0===e&&(e={}),{name:"flip",options:e,async fn(t){var s,i;const{placement:n,middlewareData:r,rects:o,initialPlacement:a,platform:c,elements:l}=t,{mainAxis:h=!0,crossAxis:u=!0,fallbackPlacements:d,fallbackStrategy:p="bestFit",fallbackAxisSideDirection:m="none",flipAlignment:g=!0,...f}=Ft(e,t);if(null!=(s=r.arrow)&&s.alignmentOffset)return{};const b=Rt(n),y=qt(a),v=Rt(a)===a,w=await(null==c.isRTL?void 0:c.isRTL(l.floating)),T=d||(v||!g?[Yt(a)]:function(e){const t=Yt(e);return[Wt(e),t,Wt(t)]}(a)),S="none"!==m;!d&&S&&T.push(...function(e,t,s,i){const n=Bt(e);let r=function(e,t,s){switch(e){case"top":case"bottom":return s?t?Kt:Ht:t?Ht:Kt;case"left":case"right":return t?Gt:Jt;default:return[]}}(Rt(e),"start"===s,i);return n&&(r=r.map(e=>e+"-"+n),t&&(r=r.concat(r.map(Wt)))),r}(a,g,m,w));const C=[a,...T],O=await Qt(t,f),E=[];let A=(null==(i=r.flip)?void 0:i.overflows)||[];if(h&&E.push(O[b]),u){const e=zt(n,o,w);E.push(O[e[0]],O[e[1]])}if(A=[...A,{placement:n,overflows:E}],!E.every(e=>e<=0)){var x,M;const e=((null==(x=r.flip)?void 0:x.index)||0)+1,t=C[e];if(t&&("alignment"!==u||y===qt(t)||A.every(e=>qt(e.placement)!==y||e.overflows[0]>0)))return{data:{index:e,overflows:A},reset:{placement:t}};let s=null==(M=A.filter(e=>e.overflows[0]<=0).sort((e,t)=>e.overflows[1]-t.overflows[1])[0])?void 0:M.placement;if(!s)switch(p){case"bestFit":{var k;const e=null==(k=A.filter(e=>{if(S){const t=qt(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:k[0];e&&(s=e);break}case"initialPlacement":s=a}if(n!==s)return{reset:{placement:s}}}return{}}}},Gs=e=>{Object.assign(e,{show(){this.cancelBehaviourOpen?.(),this.openValue=!0},hide(){this.openValue=!1},toggle(){this.cancelBehaviourOpen?.(),this.openValue=!this.openValue},setupFloatingUI({trigger:e,popover:t,strategy:s}){this.floatingUICleanup=function(e,t,s,i){void 0===i&&(i={});const{ancestorScroll:n=!0,ancestorResize:r=!0,elementResize:o="function"==typeof ResizeObserver,layoutShift:a="function"==typeof IntersectionObserver,animationFrame:c=!1}=i,l=ks(e),h=n||r?[...l?As(l):[],...As(t)]:[];h.forEach(e=>{n&&e.addEventListener("scroll",s,{passive:!0}),r&&e.addEventListener("resize",s)});const u=l&&a?function(e,t){let s,i=null;const n=ns(e);function r(){var e;clearTimeout(s),null==(e=i)||e.disconnect(),i=null}return function o(a,c){void 0===a&&(a=!1),void 0===c&&(c=1),r();const l=e.getBoundingClientRect(),{left:h,top:u,width:d,height:p}=l;if(a||t(),!d||!p)return;const m={rootMargin:-Lt(u)+"px "+-Lt(n.clientWidth-(h+d))+"px "+-Lt(n.clientHeight-(u+p))+"px "+-Lt(h)+"px",threshold:kt(0,Mt(1,c))||1};let g=!0;function f(t){const i=t[0].intersectionRatio;if(i!==c){if(!g)return o();i?o(!1,i):s=setTimeout(()=>{o(!1,1e-7)},1e3)}1!==i||zs(l,e.getBoundingClientRect())||o(),g=!1}try{i=new IntersectionObserver(f,{...m,root:n.ownerDocument})}catch(e){i=new IntersectionObserver(f,m)}i.observe(e)}(!0),r}(l,s):null;let d,p=-1,m=null;o&&(m=new ResizeObserver(e=>{let[i]=e;i&&i.target===l&&m&&(m.unobserve(t),cancelAnimationFrame(p),p=requestAnimationFrame(()=>{var e;null==(e=m)||e.observe(t)})),s()}),l&&!c&&m.observe(l),m.observe(t));let g=c?_s(e):null;return c&&function t(){const i=_s(e);g&&!zs(g,i)&&s(),g=i,d=requestAnimationFrame(t)}(),s(),()=>{var e;h.forEach(e=>{n&&e.removeEventListener("scroll",s),r&&e.removeEventListener("resize",s)}),null==u||u(),null==(e=m)||e.disconnect(),m=null,c&&cancelAnimationFrame(d)}}(e,t,()=>{((e,t,s)=>{const i=new Map,n={platform:Us,...s},r={...n.platform,_c:i};return(async(e,t,s)=>{const{placement:i="bottom",strategy:n="absolute",middleware:r=[],platform:o}=s,a=r.filter(Boolean),c=await(null==o.isRTL?void 0:o.isRTL(t));let l=await o.getElementRects({reference:e,floating:t,strategy:n}),{x:h,y:u}=Xt(l,i,c),d=i,p={},m=0;for(let s=0;s{const n={left:`${e}px`,top:`${s}px`,position:i};Object.assign(t.style,n)})})},openValueChanged(){this.disabledValue||(this.openValue?(this.preparePopoverOpenAnimation?.(),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()))}})},Js=class extends i.xI{static targets=["button","popover"];static values={placement:{type:String,default:"bottom-end"},open:{type:Boolean,default:!1},autoPlacement:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1},size:{type:Number,default:24},perLine:{type:Number,default:9}};initialize(){this.onEmojiSelect=this.onEmojiSelect.bind(this),this.pickerLoaded=!1,this.pickerLoadPromise=null,this.connected=!1,super.initialize()}connect(){this.connected=!0,Gs(this),this.setupFloatingUI({trigger:this.buttonTarget,popover:this.popoverTarget,strategy:"absolute"}),super.connect()}disconnect(){this.connected=!1,this.pickerLoadPromise=null,this.floatingUICleanup(),super.disconnect()}onEmojiSelect(e){this.dispatch("selected",{detail:e.native}),this.hide()}onClickOutside(e){this.openValue&&e.target.nodeType&&!1===this.element.contains(e.target)&&(this.openValue=!1)}async onPopoverOpened(){await this.loadPicker()}async loadPicker(){if(this.pickerLoaded)return;this.pickerLoadPromise||=this.loadPickerDependencies();const{Picker:e,i18n:t}=await this.pickerLoadPromise;this.connected&&!this.pickerLoaded&&(this.popoverTarget.appendChild(this.buildPicker(e,t)),this.pickerLoaded=!0)}async loadPickerDependencies(){const[e,t]=await Promise.all([s.e(160).then(s.bind(s,405)),this.loadI18n()]);return{Picker:e.Picker,i18n:t.default||t}}loadI18n(){return"es"===Hellotext.business.locale?s.e(437).then(s.t.bind(s,9,19)):s.e(200).then(s.t.bind(s,714,19))}buildPicker(e,t){return new e({onEmojiSelect:this.onEmojiSelect,theme:"light",dynamicWidth:!0,previewPosition:"none",skinTonePosition:"none",emojiSize:this.sizeValue,perLine:this.perLineValue,i18n:t})}get middlewares(){return[Ws(5),Hs({padding:24}),(e={allowedPlacements:["top","bottom"]},void 0===e&&(e={}),{name:"autoPlacement",options:e,async fn(t){var s,i,n;const{rects:r,middlewareData:o,placement:a,platform:c,elements:l}=t,{crossAxis:h=!1,alignment:u,allowedPlacements:d=xt,autoAlignment:p=!0,...m}=Ft(e,t),g=void 0!==u||d===xt?function(e,t,s){return(e?[...s.filter(t=>Bt(t)===e),...s.filter(t=>Bt(t)!==e)]:s.filter(e=>Rt(e)===e)).filter(s=>!e||Bt(s)===e||!!t&&Wt(s)!==s)}(u||null,p,d):d,f=await Qt(t,m),b=(null==(s=o.autoPlacement)?void 0:s.index)||0,y=g[b];if(null==y)return{};const v=zt(y,r,await(null==c.isRTL?void 0:c.isRTL(l.floating)));if(a!==y)return{reset:{placement:g[0]}};const w=[f[Rt(y)],f[v[0]],f[v[1]]],T=[...(null==(i=o.autoPlacement)?void 0:i.overflows)||[],{placement:y,overflows:w}],S=g[b+1];if(S)return{data:{index:b+1,overflows:T},reset:{placement:S}};const C=T.map(e=>{const t=Bt(e.placement);return[e.placement,t&&h?e.overflows.slice(0,2).reduce((e,t)=>e+t,0):e.overflows[0],e.overflows]}).sort((e,t)=>e[1]-t[1]),O=(null==(n=C.filter(e=>e[2].slice(0,Bt(e[0])?2:3).every(e=>e<=0))[0])?void 0:n[0])||C[0][0];return O!==a?{data:{index:b+1,overflows:T},reset:{placement:O}}:{}}})];var e}};class Ys{static get endpoint(){return f.endpoint("public/webchats/:id/messages")}constructor(e){this.webchatId=e}async index(e){const t=new URL(this.url);return Object.entries(e).forEach(([e,s])=>{t.searchParams.append(e,s)}),await fetch(t,{method:"GET",headers:wt.headers})}catchUp(e){return this.index({after_id:e,session:wt.session})}async create(e){const t=await fetch(this.url,{method:"POST",headers:{Authorization:`Bearer ${wt.business.id}`},body:e});return new v(t.ok,t)}markAsSeen(e=null){const t=e?this.url+`/${e}`:this.url+"/seen";fetch(t,{method:"PATCH",headers:wt.headers,body:JSON.stringify({session:wt.session})})}get url(){return Ys.endpoint.replace(":id",this.webchatId)}}const Zs=Ys;class Xs{static webSocket;static channels=new Set;static messageHandlers=new Set;static disconnectHandlers=new Set;static subscriptionConfirmHandlers=new Set;static reconnectTimeout=null;static reconnectAttempts=0;static reconnectBaseDelay=500;static reconnectMaxDelay=1e4;static reconnectJitter=.3;static needsResubscribe=!1;constructor(){Xs.channels.add(this)}send({command:e,identifier:t,data:s}){const i={command:e,identifier:JSON.stringify(t),data:JSON.stringify(s||{})},n=Xs.ensureWebSocket(),r=JSON.stringify(i);n.readyState===WebSocket.OPEN?n.send(r):n.addEventListener("open",()=>{n.send(r)})}onMessage(e){const t=t=>{const s=JSON.parse(t.data),{type:i,message:n}=s;this.ignoredEvents.includes(i)||e(n)};Xs.messageHandlers.add(t),Xs.ensureWebSocket().addEventListener("message",t)}onDisconnect(e){Xs.disconnectHandlers.add(e)}onSubscriptionConfirmed(e){Xs.subscriptionConfirmHandlers.add(e)}get webSocket(){return Xs.ensureWebSocket()}static ensureWebSocket(){return this.webSocket&&!this.closedWebSocket(this.webSocket)?this.webSocket:(this.webSocket&&(this.needsResubscribe=!0),this.openWebSocket())}static openWebSocket(){this.clearReconnectTimeout();const e=new WebSocket(f.actionCableUrl);return this.webSocket=e,this.installWebSocketHandlers(e),e}static installWebSocketHandlers(e){e.addEventListener("open",()=>this.handleOpen(e)),e.addEventListener("close",()=>this.handleDisconnect(e)),e.addEventListener("error",()=>this.handleDisconnect(e)),e.addEventListener("message",e=>this.handleControlMessage(e)),this.messageHandlers.forEach(t=>{e.addEventListener("message",t)})}static handleOpen(e){e===this.webSocket&&(this.reconnectAttempts=0,this.needsResubscribe&&(this.needsResubscribe=!1,this.resubscribeChannels()))}static handleControlMessage(e){let t;try{t=JSON.parse(e.data)}catch{return}"confirm_subscription"===t.type&&this.subscriptionConfirmHandlers.forEach(e=>e(t.identifier))}static handleDisconnect(e){e===this.webSocket&&(this.disconnectHandlers.forEach(e=>e()),this.webSocket=null,this.needsResubscribe=!0,this.scheduleReconnect())}static scheduleReconnect(){this.reconnectTimeout||(this.reconnectTimeout=setTimeout(()=>{this.reconnectTimeout=null,this.reconnectAttempts+=1,this.openWebSocket()},this.reconnectDelay))}static clearReconnectTimeout(){this.reconnectTimeout&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null)}static resubscribeChannels(){this.channels.forEach(e=>{const t=e.resubscribe||e.subscribe;"function"==typeof t&&t.call(e)})}static closedWebSocket(e){return e.readyState===WebSocket.CLOSED||e.readyState===WebSocket.CLOSING}static get reconnectDelay(){const e=Math.min(this.reconnectMaxDelay,this.reconnectBaseDelay*2**this.reconnectAttempts);return e+Math.round(e*this.reconnectJitter*Math.random())}get ignoredEvents(){return["ping","confirm_subscription","welcome"]}}const Qs=Xs,ei=class extends Qs{constructor(e,t,s){super(),this.id=e,this.session=t,this.conversation=s,this.subscribed=!1,this.awaitingReconnectConfirmation=!1,this.reconnectCallbacks=new Set,this.onSubscriptionConfirmed(e=>this.handleSubscriptionConfirmed(e)),this.subscribe()}subscribe(){this.subscribed=!0;const e={channel:"WebchatChannel",id:this.id,session:this.session,conversation:this.conversation};this.send({command:"subscribe",identifier:e})}unsubscribe(){this.subscribed=!1;const e={channel:"WebchatChannel",id:this.id,session:this.session,conversation:this.conversation};this.send({command:"unsubscribe",identifier:e})}resubscribe(){!1!==this.subscribed&&(this.awaitingReconnectConfirmation=!0,this.subscribe())}onReconnect(e){this.reconnectCallbacks.add(e)}handleSubscriptionConfirmed(e){this.awaitingReconnectConfirmation&&this.matchesIdentifier(e)&&(this.awaitingReconnectConfirmation=!1,this.reconnectCallbacks.forEach(e=>e()))}matchesIdentifier(e){let t;try{t="string"==typeof e?JSON.parse(e):e}catch{return!1}return"WebchatChannel"===t.channel&&t.id===this.id&&t.session===this.session&&t.conversation===this.conversation}startTypingIndicator(){const e={channel:"WebchatChannel",id:this.id,session:this.session,conversation:this.conversation};this.send({command:"message",identifier:e,data:{action:"started_typing"}})}stopTypingIndicator(){const e={channel:"WebchatChannel",id:this.id,session:this.session,conversation:this.conversation};this.send({command:"typing:stop",identifier:e,data:{action:"stopped_typing"}})}onMessage(e){super.onMessage(t=>{"message"===t.type&&e(t)})}onReaction(e){super.onMessage(t=>{"reaction.create"!==t.type&&"reaction.destroy"!==t.type||e(t)})}onTypingStart(e){super.onMessage(t=>{"started_typing"===t.type&&e(t)})}updateSubscriptionWith(e){this.unsubscribe(),setTimeout(()=>{this.conversation=e,this.subscribe()},1e3)}},ti=e=>{Object.assign(e,{scheduleBehaviourOpen(){if(!this.shouldAutoOpenFromBehaviour())return;const 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(){const 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--${this.idValue}--auto-opened`},sessionKey(){return`hellotext--webchat--${this.idValue}--auto-opened-session`}})},si=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){const t=this.openingSequenceMessages[e];if(!t)return;const s=1e3*this.openingSequenceMessageDelay(t);this.openingSequenceTimeout=window.setTimeout(()=>{this.openingSequenceTimeout=null,this.openingSequenceCancelled||(this.revealOpeningSequenceMessage(t),this.playOpeningSequenceMessageAt(e+1))},s)},revealOpeningSequenceMessage(e){this.messagesContainerTarget.insertBefore(e,this.messageTemplateTarget),e.hidden=!1,this.recordOpeningSequenceMessage(e),this.scrollOpeningSequenceToBottom()},recordOpeningSequenceMessage(e){const t=e.dataset.openingSequenceMessageId;t&&!this.revealedOpeningSequenceMessageIds.includes(t)&&this.revealedOpeningSequenceMessageIds.push(t)},openingSequenceMessageDelay(e){const 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=[]}})},ii=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){const t=e+1;if(t>=this.teaserMessages.length)return;const s=this.teaserMessages[e],i=this.teaserPresentationDelay(s);this.teaserCycleTimeout=window.setTimeout(()=>{this.teaserCycleTimeout=null,this.showTeaserMessage(t),this.scheduleNextTeaserMessage(t)},i)},showTeaserMessage(e){this.teaserMessages.forEach((t,s)=>{t.classList.toggle("hidden",s!==e)})},stopTeaserCycle(){null!==this.teaserCycleTimeout&&void 0!==this.teaserCycleTimeout&&(window.clearTimeout(this.teaserCycleTimeout),this.teaserCycleTimeout=null)},teaserMessageDelay(e){const t=Number(e.dataset.delaySeconds||0);return Number.isFinite(t)?t:0},teaserPresentationDelay(e){return Math.max(1e3*this.teaserMessageDelay(e),250)},hasRenderedConversationMessages(){let e=[];try{e=Array.from(this.messageTargets||[])}catch(t){e=[]}return e.some(e=>e!==this.messageTemplateTarget)},teaserSeenKey(){const e=this.hasTeaserTarget?this.teaserTarget.dataset.teaserVersion:"",t=e?`:${e}`:"";return`hellotext:webchat:${this.idValue||this.element.id}:teaser-seen${t}`},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())}})},ni={hour:"numeric",minute:"2-digit"},ri=/Android|iPhone|iPad|iPod/i,oi={capture:!0,passive:!0},ai=class extends i.xI{static messageTimestampFormatters={};static 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};static classes=["fadeOut"];static targets=["trigger","popover","input","attachmentInput","attachmentButton","errorMessageContainer","attachmentTemplate","attachmentContainer","attachment","messageTemplate","messagesContainer","title","attachmentImage","footer","toolbar","message","unreadCounter","typingIndicator","typingIndicatorTemplate","teaser","teaserMessage","inboundMessageTeaser","inboundMessageTeaserBody","openingSequence","openingSequenceMessage"];initialize(){this.messagesAPI=new Zs(this.idValue),this.webChatChannel=new ei(this.idValue,wt.session,this.conversationIdValue),this.files=[],this.messageIds=new Set,this.catchUpAfterMessageId=null,this.fetchingCatchUpMessages=!1,this.onMessageReceived=this.onMessageReceived.bind(this),this.onMessageReaction=this.onMessageReaction.bind(this),this.onTypingStart=this.onTypingStart.bind(this),this.captureCatchUpCursor=this.captureCatchUpCursor.bind(this),this.catchUpMessages=this.catchUpMessages.bind(this),this.onScroll=this.onScroll.bind(this),this.onOutboundMessageSent=this.onOutboundMessageSent.bind(this),this.closePopoverOnEscape=this.closePopoverOnEscape.bind(this),this.broadcastChannel=new BroadcastChannel(`hellotext--webchat--${this.idValue}`),this.webChatChannel.onDisconnect(this.captureCatchUpCursor),this.webChatChannel.onReconnect(this.catchUpMessages),super.initialize()}connect(){ti(this),Gs(this),si(this),ii(this),this.setupFloatingUI({trigger:this.triggerTarget,popover:this.popoverTarget}),this.hasTeaserTarget&&this.setupFloatingUI({trigger:this.triggerTarget,popover:this.teaserTarget,strategy:"absolute"}),this.setupTeaser(),this.setupOpeningSequence(),this.localizeMessageTimestamps(),this.webChatChannel.onMessage(this.onMessageReceived),this.webChatChannel.onTypingStart(this.onTypingStart),this.webChatChannel.onReaction(this.onMessageReaction),this.setupMessagesContainerScrollIsolation(),this.messagesContainerTarget.addEventListener("scroll",this.onScroll),this.messagesContainerTarget.addEventListener("wheel",this.stopHostScrollPropagation,oi),this.messagesContainerTarget.addEventListener("touchmove",this.stopHostScrollPropagation,oi),this.shouldOpenOnMount&&(this.openValue=!0),wt.eventEmitter.dispatch("webchat:mounted"),this.broadcastChannel.addEventListener("message",this.onOutboundMessageSent),window.addEventListener("keydown",this.closePopoverOnEscape,!0),this.scheduleBehaviourOpen(),super.connect()}disconnect(){this.cancelBehaviourOpen(),this.clearPopoverOpenAnimation(),this.teardownTeaser(),this.teardownOpeningSequence(),this.broadcastChannel.removeEventListener("message",this.onOutboundMessageSent),this.messagesContainerTarget.removeEventListener("scroll",this.onScroll),this.messagesContainerTarget.removeEventListener("wheel",this.stopHostScrollPropagation,oi),this.messagesContainerTarget.removeEventListener("touchmove",this.stopHostScrollPropagation,oi),window.removeEventListener("keydown",this.closePopoverOnEscape,!0),this.clearTypingIndicator(),this.broadcastChannel.close(),this.floatingUICleanup(),super.disconnect()}setupMessagesContainerScrollIsolation(){this.messagesContainerTarget.style.overscrollBehavior="contain",this.messagesContainerTarget.style.webkitOverflowScrolling="touch",this.messagesContainerTarget.style.touchAction="pan-y",this.messagesContainerTarget.setAttribute("data-lenis-prevent",""),this.messagesContainerTarget.setAttribute("data-lenis-prevent-wheel",""),this.messagesContainerTarget.setAttribute("data-lenis-prevent-touch","")}stopHostScrollPropagation(e){e.stopPropagation()}onTypingStart(){if(this.typingIndicatorVisible)return this.resetTypingIndicatorTimer();this.showTypingIndicator()}showOptimisticTypingIndicator(){this.typingIndicatorVisible||this.showTypingIndicator()}showTypingIndicator(){this.clearTypingIndicator(),this.typingIndicatorVisible=!0;const e=this.typingIndicatorTemplateTarget.cloneNode(!0);e.setAttribute("data-hellotext--webchat-target","typingIndicator"),e.style.display="flex",this.messagesContainerTarget.appendChild(e),requestAnimationFrame(()=>{this.messagesContainerTarget.scroll({top:this.messagesContainerTarget.scrollHeight,behavior:"instant"})});const t=this.typingIndicatorKeepAliveValue;this.incomingTypingIndicatorTimeout=setTimeout(()=>{this.clearTypingIndicator()},t)}resetTypingIndicatorTimer(){if(!this.typingIndicatorVisible)return;clearTimeout(this.incomingTypingIndicatorTimeout),clearTimeout(this.optimisticTypingTimeout);const e=this.typingIndicatorKeepAliveValue;this.incomingTypingIndicatorTimeout=setTimeout(()=>{this.clearTypingIndicator()},e)}clearTypingIndicator(){this.hasTypingIndicatorTarget&&this.typingIndicatorTarget.remove(),this.typingIndicatorVisible=!1,clearTimeout(this.incomingTypingIndicatorTimeout),clearTimeout(this.optimisticTypingTimeout)}onMessageInputChange(){this.resizeInput(),clearTimeout(this.typingIndicatorTimeout),this.hasSentTypingIndicator||(this.webChatChannel.startTypingIndicator(),this.hasSentTypingIndicator=!0),this.typingIndicatorTimeout=setTimeout(()=>{this.hasSentTypingIndicator=!1},3e3)}onOutboundMessageSent(e){const{data:t}=e,s={"message:sent":e=>{const t=(new DOMParser).parseFromString(e.element,"text/html").body.firstElementChild;this.localizeMessageTimestamps(t),this.typingIndicatorVisible&&this.hasTypingIndicatorTarget?this.messagesContainerTarget.insertBefore(t,this.typingIndicatorTarget):this.messagesContainerTarget.appendChild(t),t.scrollIntoView({behavior:"instant"})},"message:failed":e=>{const t=this.messagesContainerTarget.querySelector(`#${e.id}`);this.markMessageFailed(t,e.reason)}};s[t.type]?s[t.type](t):console.log(`Unhandled message event: ${t.type}`)}async onScroll(){if(this.messagesContainerTarget.scrollTop>300||!this.nextPageValue||this.fetchingNextPage)return;this.fetchingNextPage=!0;const e=await this.messagesAPI.index({page:this.nextPageValue,session:wt.session}),{next:t,messages:s}=await e.json();this.nextPageValue=t,this.oldScrollHeight=this.messagesContainerTarget.scrollHeight,s.forEach(e=>{const{body:t,attachments:s}=e,i=e.created_at||e.createdAt,n=this.messageTemplateTarget.cloneNode(!0);n.classList.add("hellotext--webchat-message"),n.setAttribute("data-hellotext--webchat-target","message"),n.setAttribute("data-id",e.id),i&&n.setAttribute("data-created-at",i),n.style.removeProperty("display"),ot(n.querySelector("[data-body]"),t),"received"===e.state?n.classList.add("received"):n.classList.remove("received"),s&&s.forEach(e=>{const t=this.attachmentImageTarget.cloneNode(!0);t.removeAttribute("data-hellotext--webchat-target"),t.src=e,t.style.display="block",this.messageAttachmentsContainer(n)?.appendChild(t)}),n.setAttribute("data-body",t),this.localizeMessageTimestamp(n.querySelector("[data-message-timestamp]"),i),this.messagesContainerTarget.prepend(n)}),this.messagesContainerTarget.scroll({top:this.messagesContainerTarget.scrollHeight-this.oldScrollHeight,behavior:"instant"}),this.fetchingNextPage=!1}onClickOutside(e){d.mode===u.POPOVER&&this.openValue&&e.target.nodeType&&!1===this.element.contains(e.target)&&(this.openValue=!1)}closePopover(){this.clearPopoverOpenAnimation(),this.popoverTarget.classList.remove(...this.fadeOutClasses),this.openValue=!1}preparePopoverOpenAnimation(){this.clearPopoverOpenAnimation(),this.popoverTarget.classList.remove(...this.fadeOutClasses),this.popoverTarget.classList.add("hellotext--webchat-popover-opening"),this.popoverOpenAnimationTimeout=setTimeout(()=>{this.popoverTarget.classList.remove("hellotext--webchat-popover-opening"),this.popoverOpenAnimationTimeout=null},120)}clearPopoverOpenAnimation(){this.popoverOpenAnimationTimeout&&(clearTimeout(this.popoverOpenAnimationTimeout),this.popoverOpenAnimationTimeout=null),this.popoverTarget?.classList.remove("hellotext--webchat-popover-opening")}onPopoverOpened(){this.popoverTarget.classList.remove(...this.fadeOutClasses),this.dismissTeaserForSession?.(),this.onMobile||this.focusComposeInput(),this.scrolled||(requestAnimationFrame(()=>{this.messagesContainerTarget.scroll({top:this.messagesContainerTarget.scrollHeight,behavior:"instant"})}),this.scrolled=!0),wt.eventEmitter.dispatch("webchat:opened"),localStorage.setItem(`hellotext--webchat--${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())}onPopoverClosed(){this.clearPopoverOpenAnimation(),wt.eventEmitter.dispatch("webchat:closed"),localStorage.setItem(`hellotext--webchat--${this.idValue}`,"closed")}onMessageReaction(e){const{message:t,reaction:s,type:i}=e,n=this.messageTargets.find(e=>e.dataset.id===t).querySelector("[data-reactions]");if("reaction.destroy"===i)return n.querySelector(`[data-id="${s.id}"]`).remove();if(n.querySelector(`[data-id="${s.id}"]`))n.querySelector(`[data-id="${s.id}"]`).innerText=s.emoji;else{const e=document.createElement("span");e.innerText=s.emoji,e.setAttribute("data-id",s.id),n.appendChild(e)}}onMessageReceived(e,t={}){const{id:s,body:i,attachments:n,teaser:r}=e,o=e.created_at||e.createdAt;if(!this.claimMessageId(s))return;if(this.hideTeaser?.(),e.carousel)return this.insertCarouselMessage(e,t);const a=this.messageTemplateTarget.cloneNode(!0);a.classList.add("hellotext--webchat-message"),a.style.display="flex",ot(a.querySelector("[data-body]"),i),a.setAttribute("data-id",s),a.setAttribute("data-hellotext--webchat-target","message"),this.setMessageCreatedAt(a,o),this.localizeMessageTimestamp(a.querySelector("[data-message-timestamp]"),o),n&&n.forEach(e=>{const t=this.attachmentImageTarget.cloneNode(!0);t.src=e,t.style.display="block",this.messageAttachmentsContainer(a)?.appendChild(t)}),this.clearTypingIndicator(),this.insertMessageElement(a),wt.eventEmitter.dispatch("webchat:message:received",{...e,body:a.querySelector("[data-body]").innerText}),!1!==t.scroll&&a.scrollIntoView({behavior:"smooth"}),this.updateMessageTeaser(r),this.openValue?this.messagesAPI.markAsSeen(s):this.incrementUnreadCounter()}claimMessageId(e){const t=this.messageTargets||[];return!this.messageIds.has(e)&&(this.messageIds.add(e),!t.some(t=>t.dataset.id===e))}captureCatchUpCursor(){this.catchUpAfterMessageId=this.lastRenderedMessageId}async catchUpMessages(){const e=this.catchUpAfterMessageId;if(e&&!this.fetchingCatchUpMessages){this.fetchingCatchUpMessages=!0;try{const t=await this.messagesAPI.catchUp(e),{messages:s=[]}=await t.json();s.forEach(e=>this.onMessageReceived(e,{scroll:!1})),this.catchUpAfterMessageId=this.lastRenderedMessageId}finally{this.fetchingCatchUpMessages=!1}}}get lastRenderedMessageId(){const e=this.persistedMessageElements;return e[e.length-1]?.dataset.id||null}get persistedMessageElements(){return Array.from(this.messagesContainerTarget.querySelectorAll(".hellotext--webchat-message[data-id]"))}setMessageCreatedAt(e,t){t&&e.setAttribute("data-created-at",t)}insertMessageElement(e){const t=this.nextMessageElementFor(e);t?this.messagesContainerTarget.insertBefore(e,t):this.messagesContainerTarget.appendChild(e)}nextMessageElementFor(e){const t=Date.parse(e.dataset.createdAt);return Number.isNaN(t)?null:this.persistedMessageElements.find(s=>{if(s===e)return!1;const i=Date.parse(s.dataset.createdAt);return!Number.isNaN(i)&&i>t})}updateMessageTeaser(e){this.messageTeaserValue=e,this.messageTeaserValue&&this.hasTeaserTarget&&this.hasInboundMessageTeaserTarget&&this.hasInboundMessageTeaserBodyTarget&&(this.teaserMessageTargets.forEach(e=>e.classList.add("hidden")),this.inboundMessageTeaserBodyTarget.textContent=this.messageTeaserValue,this.inboundMessageTeaserTarget.classList.remove("hidden"),this.teaserTarget.classList.toggle("invisible",this.openValue))}insertCarouselMessage(e,t={}){const s=e.html,i=e.created_at||e.createdAt,n=function(e){return rt(e,nt)}(s).firstElementChild;n.classList.add("hellotext--webchat-message"),n.setAttribute("data-id",e.id),n.setAttribute("data-hellotext--webchat-target","message"),this.setMessageCreatedAt(n,i),this.localizeMessageTimestamps(n),this.clearTypingIndicator(),this.insertMessageElement(n),!1!==t.scroll&&n.scrollIntoView({behavior:"smooth"}),wt.eventEmitter.dispatch("webchat:message:received",{...e,body:n.querySelector("[data-body]")?.innerText||""}),this.updateMessageTeaser(e.teaser),this.openValue?this.messagesAPI.markAsSeen(e.id):this.incrementUnreadCounter()}resizeInput(){this.inputTarget.style.height="auto";const e=this.inputTarget.scrollHeight;this.inputTarget.style.height=`${Math.min(e,96)}px`}async sendQuickReplyMessage({detail:{id:e,product:t,buttonId:s,body:i,cardElement:n}}){this.dismissTeaserForSession?.();const r=new FormData;r.append("message[body]",i),e&&r.append("message[replied_to]",e),t&&r.append("message[product]",t),s&&r.append("message[button]",s),r.append("session",wt.session),r.append("locale",a.toString()),this.appendOpeningSequenceMessageIds(r);const o=this.buildMessageElement(),c=n?.querySelector("img")?.cloneNode(!0);o.querySelector("[data-body]").innerText=i,c&&(c.removeAttribute("width"),c.removeAttribute("height"),this.messageAttachmentsContainer(o)?.appendChild(c)),this.typingIndicatorVisible&&this.hasTypingIndicatorTarget?this.messagesContainerTarget.insertBefore(o,this.typingIndicatorTarget):this.messagesContainerTarget.appendChild(o),o.scrollIntoView({behavior:"smooth"}),this.broadcastChannel.postMessage({type:"message:sent",element:o.outerHTML});const l=await this.messagesAPI.create(r);if(l.failed)return clearTimeout(this.optimisticTypingTimeout),this.markMessageFailedFromResponse(l,o);const h=await l.json();this.dispatch("set:id",{target:o,detail:h.id}),this.localizeMessageTimestamp(o.querySelector("[data-message-timestamp]"),h.created_at||h.createdAt),this.clearRevealedOpeningSequenceMessageIds();const u={id:h.id,body:i,attachments:c?[c.src]:[],replied_to:e,product:t,button:s,type:"quick_reply"};wt.eventEmitter.dispatch("webchat:message:sent",u)}async sendTeaserQuickReply(e){e.preventDefault(),e.stopPropagation();const t=e.currentTarget,s=(t.dataset.value||"").trim(),i=[t.dataset.text,t.textContent].map(e=>(e||"").trim()).find(e=>e.length>0),n=s||i;if(!n)return;this.dismissTeaserForSession?.(),this.show();const r=(t.dataset.type||"").trim()||"quick_reply",o=new FormData;o.append("message[body]",n),o.append("session",wt.session),o.append("locale",a.toString()),this.appendOpeningSequenceMessageIds(o);const c=this.buildMessageElement();c.querySelector("[data-body]").innerText=n,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));const l=await this.messagesAPI.create(o);if(l.failed)return clearTimeout(this.optimisticTypingTimeout),this.markMessageFailedFromResponse(l,c);const h=await l.json();c.setAttribute("data-id",h.id),this.localizeMessageTimestamp(c.querySelector("[data-message-timestamp]"),h.created_at||h.createdAt),this.clearRevealedOpeningSequenceMessageIds(),wt.eventEmitter.dispatch("webchat:message:sent",{id:h.id,body:n,attachments:[],type:"quick_reply",teaser:{text:i||n,value:s||n,type:r}}),h.conversation&&h.conversation!==this.conversationIdValue&&(this.conversationIdValue=h.conversation,this.webChatChannel.updateSubscriptionWith(this.conversationIdValue)),this.typingIndicatorVisible&&this.resetTypingIndicatorTimer()}async sendMessage(e){const t={body:this.inputTarget.value,attachments:this.files};if(0===this.inputTarget.value.trim().length&&0===this.files.length)return void(e&&e.target&&e.preventDefault());this.dismissTeaserForSession?.();const s=new FormData;this.inputTarget.value.trim().length>0?s.append("message[body]",this.inputTarget.value):delete t.body,this.files.forEach(e=>{s.append("message[attachments][]",e)}),s.append("session",wt.session),s.append("locale",a.toString()),this.appendOpeningSequenceMessageIds(s);const i=this.buildMessageElement();this.inputTarget.value.trim().length>0?i.querySelector("[data-body]").innerText=this.inputTarget.value:i.querySelector("[data-message-bubble]").remove();const n=this.attachmentContainerTarget.querySelectorAll("img");n.length>0&&n.forEach(e=>{this.messageAttachmentsContainer(i)?.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.focusComposeInput(),this.typingIndicatorVisible||(clearTimeout(this.optimisticTypingTimeout),this.optimisticTypingTimeout=setTimeout(()=>{this.showOptimisticTypingIndicator()},this.optimisticTypingIndicatorWaitValue));const r=await this.messagesAPI.create(s);if(r.failed)return clearTimeout(this.optimisticTypingTimeout),this.markMessageFailedFromResponse(r,i);const o=await r.json();i.setAttribute("data-id",o.id),t.id=o.id,this.localizeMessageTimestamp(i.querySelector("[data-message-timestamp]"),o.created_at||o.createdAt),this.clearRevealedOpeningSequenceMessageIds(),wt.eventEmitter.dispatch("webchat:message:sent",t),o.conversation!==this.conversationIdValue&&(this.conversationIdValue=o.conversation,this.webChatChannel.updateSubscriptionWith(this.conversationIdValue)),this.typingIndicatorVisible&&this.resetTypingIndicatorTimer(),this.attachmentContainerTarget.style.display=""}buildMessageElement(){const e=this.messageTemplateTarget.cloneNode(!0);return e.id=`hellotext--webchat--${this.idValue}--message--${Date.now()}`,e.classList.add("received"),e.style.removeProperty("display"),e.setAttribute("data-controller","hellotext--message"),e.setAttribute("data-hellotext--webchat-target","message"),this.localizeMessageTimestamp(e.querySelector("[data-message-timestamp]"),new Date),e}focusCompose(e){const{target:t}=e,s=["button","a","input","textarea","select","label",'[role="button"]',"em-emoji-picker",'[data-hellotext--webchat--emoji-target~="popover"]','[data-controller~="hellotext--webchat--emoji"]'].join(", ");this.hasInputTarget&&!t.closest(s)&&this.focusComposeInput({moveCursorToEnd:!0})&&e.preventDefault()}closePopoverFromHeader(e){const{target:t}=e;t.closest(".hellotext--webchat-header-channel-button, .hellotext--webchat-close-button")||(e.preventDefault(),this.closePopover())}closePopoverOnEscape(e){"Escape"===e.key&&this.openValue&&(e.preventDefault(),e.stopPropagation(),this.closePopover(),this.triggerTarget?.focus?.())}async markMessageFailedFromResponse(e,t){const s=await this.messageFailureReason(e);this.markMessageFailed(t,s),this.broadcastChannel.postMessage({type:"message:failed",id:t.id,reason:s})}markMessageFailed(e,t){if(!e)return;if(e.classList.add("failed"),!t)return;const s=e.querySelector("[data-message-timestamp]");s&&(s.textContent=t)}localizeMessageTimestamps(e=this.element){e&&(e.matches?.("time[datetime][data-message-timestamp]")?[e]:Array.from(e.querySelectorAll?.("time[datetime][data-message-timestamp]")||[])).forEach(e=>this.localizeMessageTimestamp(e))}localizeMessageTimestamp(e,t=e?.getAttribute("datetime")){if(!e||!t)return;const s=t instanceof Date?t:new Date(t);Number.isNaN(s.getTime())||(e.setAttribute("datetime",s.toISOString()),e.textContent=this.formatMessageTimestamp(s))}formatMessageTimestamp(e){return this.constructor.messageTimestampFormatterFor(a.toString()).format(e)}static messageTimestampFormatterFor(e){const t=e||"default";return this.messageTimestampFormatters[t]||(this.messageTimestampFormatters[t]=this.buildMessageTimestampFormatter(e)),this.messageTimestampFormatters[t]}static buildMessageTimestampFormatter(e){try{return new Intl.DateTimeFormat(e||void 0,ni)}catch(e){return new Intl.DateTimeFormat(void 0,ni)}}async messageFailureReason(e){const t=e?.data||e?.response,s=t?.statusText||"Message failed";try{const e=t?.clone?t.clone():t,s=await(e?.json?.()),i=this.messageFailureReasonFromPayload(s);if(i)return i}catch(e){}try{const e=t?.clone?t.clone():t,i=await(e?.text?.());return this.messageFailureReasonFromText(i)||s}catch(e){return s}}messageFailureReasonFromText(e){if("string"!=typeof e)return null;const t=e.trim();if(!t||t.startsWith("<"))return null;try{return this.messageFailureReasonFromPayload(JSON.parse(t))||t}catch(e){return t}}messageFailureReasonFromPayload(e){return e?[e.error?.message,e.message,e.errors?.message,e.errors?.[0]?.message,e.errors?.[0]?.description].find(e=>"string"==typeof e&&e.trim().length>0):null}messageAttachmentsContainer(e){return e.querySelector("[data-attachments-container], [data-attachment-container]")}incrementUnreadCounter(){this.unreadCounterTarget.style.display="flex";const e=(parseInt(this.unreadCounterTarget.innerText)||0)+1;this.unreadCounterTarget.innerText=Math.min(e,9)}openAttachment(){this.attachmentInputTarget.click()}onFileInputChange(){this.errorMessageContainerTarget.style.display="none";const e=Array.from(this.attachmentInputTarget.files);this.attachmentInputTarget.value="";const t=e.find(e=>{const t=e.type.split("/")[0];return["image","video","audio"].includes(t)?this.mediaValue[t].max_sizethis.createAttachmentElement(e)),this.focusComposeInput()}createAttachmentElement(e){const t=this.attachmentElement();if(this.attachmentContainerTarget.style.display="",t.setAttribute("data-name",e.name),e.type.startsWith("image/")){const s=this.attachmentImageTarget.cloneNode(!0);s.src=URL.createObjectURL(e),s.style.display="block",t.appendChild(s),this.attachmentContainerTarget.appendChild(t),this.attachmentContainerTarget.style.display="flex"}else{const s=t.querySelector("main");s.style.height="5rem",s.style.borderRadius="0.375rem",s.style.backgroundColor="#e5e7eb",s.style.padding="0.25rem",t.querySelector("p[data-attachment-name]").innerText=e.name,this.attachmentContainerTarget.appendChild(t),this.attachmentContainerTarget.style.display="flex"}}removeAttachment({currentTarget:e}){const t=e.closest("[data-hellotext--webchat-target='attachment']");this.files=this.files.filter(e=>e.name!==t.dataset.name),this.attachmentInputTarget.value="",t.remove(),this.focusComposeInput()}attachmentTargetDisconnected(){0===this.attachmentTargets.length&&(this.attachmentContainerTarget.innerHTML="",this.attachmentContainerTarget.style.display="none")}attachmentElement(){const e=this.attachmentTemplateTarget.cloneNode(!0);return e.removeAttribute("hidden"),e.style.display="flex",e.setAttribute("data-hellotext--webchat-target","attachment"),e}onEmojiSelect({detail:e}){const t=this.inputTarget.value,s=this.inputTarget.selectionStart,i=this.inputTarget.selectionEnd;this.inputTarget.value=t.slice(0,s)+e+t.slice(i),this.inputTarget.selectionStart=this.inputTarget.selectionEnd=s+e.length,this.focusComposeInput()}focusComposeInput({moveCursorToEnd:e=!1}={}){if(!this.shouldAutofocusCompose)return!1;if(!1===this.hasInputTarget)return!1;if(void 0===this.hasInputTarget&&!this.inputTarget)return!1;if(this.inputTarget.focus(),e&&"number"==typeof this.inputTarget.selectionStart){const e=this.inputTarget.value.length;this.inputTarget.setSelectionRange(e,e)}return!0}byteToMegabyte(e){return Math.ceil(e/1024/1024)}get middlewares(){return[Ws(this.offsetValue),Hs({padding:this.paddingValue}),Ks()]}get shouldOpenOnMount(){return"opened"===localStorage.getItem(`hellotext--webchat--${this.idValue}`)&&!this.onMobile}get shouldAutofocusCompose(){return!this.usesVirtualKeyboard}get usesVirtualKeyboard(){if("undefined"==typeof navigator)return!1;const e=navigator.userAgent||"",t="MacIntel"===navigator.platform&&navigator.maxTouchPoints>1,s=ri.test(e),i=!0===navigator.userAgentData?.mobile;return s||t||i||this.hasTouchOnlyPointer}get hasTouchOnlyPointer(){return"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(pointer: coarse)").matches&&window.matchMedia("(hover: none)").matches}get onMobile(){return"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia(`(max-width: ${this.fullScreenThresholdValue}px)`).matches}},ci=i.lg.start();ci.register("hellotext--alert",St),ci.register("hellotext--form",Ct),ci.register("hellotext--popup",Et),ci.register("hellotext--webchat",ai),ci.register("hellotext--webchat--emoji",Js),ci.register("hellotext--message",Ot),window.Hellotext=wt;const li=wt}};const t={};function s(i){const n=t[i];if(void 0!==n)return n.exports;const r=t[i]={exports:{}};return e[i](r,r.exports,s),r.exports}s.m=e,(()=>{const e=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;let t;s.t=function(i,n){if(1&n&&(i=this(i)),8&n)return i;if("object"==typeof i&&i){if(4&n&&i.__esModule)return i;if(16&n&&"function"==typeof i.then)return i}const r=Object.create(null);s.r(r);const o={};t=t||[null,e({}),e([]),e(e)];for(var a=2&n&&i;("object"==typeof a||"function"==typeof a)&&!~t.indexOf(a);a=e(a))Object.getOwnPropertyNames(a).forEach(e=>o[e]=()=>i[e]);return o.default=()=>i,s.d(r,o),r}})(),s.d=(e,t)=>{if(Array.isArray(t))for(var i=0;iPromise.all(Object.keys(s.f).reduce((t,i)=>(s.f[i](e,t),t),[])),s.u=e=>({160:"webchat-emoji",200:"webchat-emoji-en",437:"webchat-emoji-es"}[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),(()=>{const e={},t="Hellotext:";s.l=(i,n,r,o)=>{if(e[i])return void e[i].push(n);let a,c;if(void 0!==r){const e=document.getElementsByTagName("script");for(var l=0;l{a.onerror=a.onload=null,clearTimeout(u);const n=e[i];if(delete e[i],a.parentNode?.removeChild(a),n?.forEach(e=>e(s)),t)return t(s)},u=setTimeout(h.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=h.bind(null,a.onerror),a.onload=h.bind(null,a.onload),c&&document.head.appendChild(a)}})(),s.r=e=>{Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{let e;s.g.importScripts&&(e=s.g.location+"");const t=s.g.document;if(!e&&t&&("SCRIPT"===t.currentScript?.tagName.toUpperCase()&&(e=t.currentScript.src),!e)){const s=t.getElementsByTagName("script");if(s.length){let t=s.length-1;for(;t>-1&&(!e||!/^http(s?):/.test(e));)e=s[t--].src}}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),s.p=e})(),(()=>{const e={792:0};s.f.j=(t,i)=>{let n=s.o(e,t)?e[t]:void 0;if(0!==n)if(n)i.push(n[2]);else{const r=new Promise((s,i)=>n=e[t]=[s,i]);i.push(n[2]=r);const o=s.p+s.u(t),a=new Error,c=i=>{if(s.o(e,t)&&(n=e[t],0!==n&&(e[t]=void 0),n)){const e=i&&("load"===i.type?"missing":i.type),s=i&&i.target&&i.target.src;a.message="Loading chunk "+t+" failed.\n("+e+": "+s+")",a.name="ChunkLoadError",a.type=e,a.request=s,n[1](a)}};s.l(o,c,"chunk-"+t,t)}};const t=(t,i)=>{let[n,r,o]=i;var a,c,l=0;if(n.some(t=>0!==e[t])){for(a in r)s.o(r,a)&&(s.m[a]=r[a]);o&&o(s)}for(t&&t(i);l +``` + +The `container` option controls where the markup is inserted. Popup placement and layout remain controlled by the dashboard configuration. A missing container or invalid selector prevents the popup from mounting. + +### Device + +The following `device` values are accepted: + +- `auto` - Requests the mobile layout for viewport widths below 768 pixels and the desktop layout otherwise. +- `mobile` - Requests the mobile layout explicitly. +- `desktop` - Requests the desktop layout explicitly. + +```js +Hellotext.initialize('PUBLIC_BUSINESS_ID', { + popup: { + device: 'mobile', + }, +}) +``` + +The requested layout and the dashboard's device targeting are separate settings. The popup still checks the actual viewport against its dashboard target before displaying. For example, requesting the mobile layout does not make a mobile-only popup appear on a desktop viewport. + +Device targeting is evaluated when the popup controller connects. Resizing the window does not automatically reload the layout or reevaluate whether the popup should appear. + +### Appearance and styles + +Configure the popup's layout, colors, typography, content, and bubble in the dashboard. The JavaScript configuration accepts `id`, `container`, and `device`. + +Hellotext.js automatically loads the popup stylesheet supplied with the public business configuration. This applies to both package imports and the script-tag bundle; no separate popup CSS import is needed. + +### Behaviour + +The server renders the bubble and dialog hidden. Once the popup is mounted and its device target matches, Hellotext.js selects the opening state: + +- With bubble mode enabled, the launcher appears first. Clicking it opens the dialog and hides the bubble. +- Without bubble mode, the dialog opens immediately. + +Closing the popup hides both the dialog and the launcher. Dismissal is remembered for that controller instance and is not persisted across page reloads. + +The popup configuration does not provide the Webchat `behaviour` options. This runtime does not implement delayed opening, first-visit rules, or once-per-session display rules. + +### Validation and submission + +Each step is validated using the browser's native input constraints, including required fields and email format. Invalid fields display their validation messages before the visitor can continue. + +Submitting the form before the last step advances to the next step after validation. The final step validates its inputs and submits the collected flow, including email, phone, custom fields, checkbox choices, and capture metadata. + +Submission buttons are disabled while the request is pending. Field-specific errors appear beside their inputs, and other failures appear in the form's error message. The visitor can correct values or retry without reentering the whole form. + +Retrying an unchanged submission reuses its idempotency key so Hellotext can recognize an earlier attempt whose response was lost. Changing the submitted data creates a new key. Requests are retried when the visitor submits again. + +### Completion and verification + +The completion screen appears when Hellotext accepts the submission. Verification or message delivery may still be pending at that point. + +Completion text can contain `{destination}` and `{channel}` placeholders. The runtime fills these using the delivery destination and channel returned by Hellotext, including any fallback route selected by the backend. + +For a queued delivery awaiting verification, the completion screen supports: + +- **Resend:** Available after an initial 60-second countdown. Subsequent cooldowns follow the server's retry interval. +- **Change email or phone:** Cancels the previous submission before returning to the relevant field. If cancellation fails, the completion screen remains visible so a replacement submission cannot start while the previous one may still deliver. + +When delivery is unnecessary, the completion screen shows saved-details copy and hides delivery actions. The runtime does not poll for verification updates or emit a completion/verification event. + +### Mounting + +Listen for `popup:mounted` to react when the popup is mounted. Register the listener before initializing Hellotext: + +```js +Hellotext.on('popup:mounted', () => { + console.log('Popup mounted') +}) + +Hellotext.initialize('PUBLIC_BUSINESS_ID') +``` + +The event fires when the popup controller connects to its markup, before the initial display state is evaluated. It also fires if the same controller reconnects. No mounting event is emitted when no popup is mounted, including when popup loading is disabled or no popup is selected. + +Mounting does not guarantee that the dialog is visible: the popup may be waiting for a bubble click or excluded by device targeting. Use `popup:opened` to react to dialog visibility. + +### Events + +Use `Hellotext.on(event, callback)` to listen for popup events. Register listeners before initialization to receive mounting and automatic opening events. + +- `popup:mounted` - Emitted when the popup is mounted, before any automatic `popup:opened` event. The dialog may remain hidden because of device targeting or bubble mode. +- `popup:opened` - Emitted when the dialog becomes visible, either automatically or after a bubble click. Showing the bubble alone does not emit this event. +- `popup:closed` - Emitted when the visitor dismisses the popup. + +These events do not include a payload. + +```js +const onPopupOpened = () => { + console.log('Popup opened') +} + +Hellotext.on('popup:opened', onPopupOpened) +Hellotext.on('popup:closed', () => { + console.log('Popup closed') +}) + +Hellotext.initialize('PUBLIC_BUSINESS_ID') +``` + +Remove a listener by passing the same callback: + +```js +Hellotext.removeEventListener('popup:opened', onPopupOpened) +``` diff --git a/index.d.ts b/index.d.ts index b2eeaa1..82a3cd8 100644 --- a/index.d.ts +++ b/index.d.ts @@ -5,6 +5,7 @@ export interface HellotextConfig { autoMount?: boolean successMessage?: boolean | string } + popup?: false | HellotextPopupConfig webchat?: false | HellotextWebchatConfig whatsappWidget?: false | HellotextWhatsAppWidgetConfig push?: false | HellotextPushConfig @@ -68,6 +69,12 @@ export interface HellotextWhatsAppWidgetConfig { appearance?: HellotextWhatsAppWidgetAppearance } +export interface HellotextPopupConfig { + id?: string + container?: string + device?: 'auto' | 'mobile' | 'desktop' +} + export interface HellotextPushConfig { serviceWorkerUrl?: string | null channelId?: string | null @@ -110,6 +117,7 @@ export interface HellotextBusinessData { style_url?: string webchat?: HellotextWebchatConfig | null whatsapp?: HellotextWhatsAppWidgetConfig | null + popup?: HellotextPopupConfig | null push?: { public_key: string } | null alert?: { html: string } | null whitelist?: string | string[] | null @@ -171,6 +179,7 @@ declare class Hellotext { static get isInitialized(): boolean static forms: any static business: HellotextBusiness + static popup: any static webchat: any static whatsapp: any static push: HellotextPush | null diff --git a/lib/api/index.cjs b/lib/api/index.cjs index 83d570c..19c7f5e 100644 --- a/lib/api/index.cjs +++ b/lib/api/index.cjs @@ -15,6 +15,7 @@ var _businesses = _interopRequireDefault(require("./businesses")); var _events = _interopRequireDefault(require("./events")); var _forms = _interopRequireDefault(require("./forms")); var _identifications = _interopRequireDefault(require("./identifications")); +var _popups = _interopRequireDefault(require("./popups")); var _webchats = _interopRequireDefault(require("./webchats")); var _whatsapp_widgets = _interopRequireDefault(require("./whatsapp_widgets")); var _acks = _interopRequireDefault(require("./acks")); @@ -52,6 +53,12 @@ class API { static get forms() { return _forms.default; } + static get popups() { + return _popups.default; + } + static get popups() { + return _popups.default; + } static get webchats() { return _webchats.default; } diff --git a/lib/api/index.js b/lib/api/index.js index e01daba..eaf5249 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -2,6 +2,7 @@ import BusinessesAPI from './businesses'; import EventsAPI from './events'; import FormsAPI from './forms'; import IdentificationsAPI from './identifications'; +import PopupsAPI from './popups'; import WebchatsAPI from './webchats'; import WhatsAppWidgetsAPI from './whatsapp_widgets'; import AcksAPI from './acks'; @@ -38,6 +39,12 @@ export default class API { static get forms() { return FormsAPI; } + static get popups() { + return PopupsAPI; + } + static get popups() { + return PopupsAPI; + } static get webchats() { return WebchatsAPI; } diff --git a/lib/api/popups.cjs b/lib/api/popups.cjs new file mode 100644 index 0000000..358de0c --- /dev/null +++ b/lib/api/popups.cjs @@ -0,0 +1,95 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; +var _core = require("../core"); +var _hellotext = _interopRequireDefault(require("../hellotext")); +var _response = require("./response"); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +class PopupsAPI { + static get endpoint() { + return _core.Configuration.endpoint('public/popups'); + } + static async get(id) { + const url = new URL(`${this.endpoint}/${id}`); + url.searchParams.append('session', _hellotext.default.session); + url.searchParams.append('locale', _core.Locale.toString()); + url.searchParams.append('device', this.runtimeDevice); + const response = await this.fetchPopup(url); + if (!response.ok) return null; + const data = await this.parsePopupResponse(response); + if (!data) return null; + if (!_hellotext.default.business.data) { + _hellotext.default.business.setData(data.business); + _hellotext.default.business.setLocale(data.locale); + } + return new DOMParser().parseFromString(data.html, 'text/html').querySelector('article'); + } + static async submit(id, data, idempotencyKey = this.idempotencyKey()) { + const response = await fetch(`${this.endpoint}/${id}/submissions`, { + method: 'POST', + headers: { + ..._hellotext.default.headers, + 'Idempotency-Key': idempotencyKey + }, + body: JSON.stringify({ + session: _hellotext.default.session, + popup_submission: data + }) + }); + return new _response.Response(response.ok, response); + } + static async resend(id, submissionId, token) { + const response = await fetch(`${this.endpoint}/${id}/submissions/${submissionId}/resend`, { + method: 'POST', + headers: _hellotext.default.headers, + body: JSON.stringify({ + token + }) + }); + return new _response.Response(response.ok, response); + } + static async cancel(id, submissionId, token) { + const response = await fetch(`${this.endpoint}/${id}/submissions/${submissionId}/cancel`, { + method: 'POST', + headers: _hellotext.default.headers, + body: JSON.stringify({ + token + }) + }); + return new _response.Response(response.ok, response); + } + static idempotencyKey() { + if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { + return crypto.randomUUID(); + } + return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; + } + static async fetchPopup(url) { + try { + return await fetch(url, { + method: 'GET', + headers: _hellotext.default.headers + }); + } catch (_) { + return { + ok: false + }; + } + } + static get runtimeDevice() { + if (_core.Configuration.popup.device !== 'auto') return _core.Configuration.popup.device; + return window.innerWidth <= 767 ? 'mobile' : 'desktop'; + } + static async parsePopupResponse(response) { + try { + return await response.json(); + } catch (_) { + return null; + } + } +} +var _default = PopupsAPI; +exports.default = _default; \ No newline at end of file diff --git a/lib/api/popups.js b/lib/api/popups.js new file mode 100644 index 0000000..332894f --- /dev/null +++ b/lib/api/popups.js @@ -0,0 +1,87 @@ +import { Configuration, Locale } from '../core'; +import Hellotext from '../hellotext'; +import { Response } from './response'; +class PopupsAPI { + static get endpoint() { + return Configuration.endpoint('public/popups'); + } + static async get(id) { + const url = new URL(`${this.endpoint}/${id}`); + url.searchParams.append('session', Hellotext.session); + url.searchParams.append('locale', Locale.toString()); + url.searchParams.append('device', this.runtimeDevice); + const response = await this.fetchPopup(url); + if (!response.ok) return null; + const data = await this.parsePopupResponse(response); + if (!data) return null; + if (!Hellotext.business.data) { + Hellotext.business.setData(data.business); + Hellotext.business.setLocale(data.locale); + } + return new DOMParser().parseFromString(data.html, 'text/html').querySelector('article'); + } + static async submit(id, data, idempotencyKey = this.idempotencyKey()) { + const response = await fetch(`${this.endpoint}/${id}/submissions`, { + method: 'POST', + headers: { + ...Hellotext.headers, + 'Idempotency-Key': idempotencyKey + }, + body: JSON.stringify({ + session: Hellotext.session, + popup_submission: data + }) + }); + return new Response(response.ok, response); + } + static async resend(id, submissionId, token) { + const response = await fetch(`${this.endpoint}/${id}/submissions/${submissionId}/resend`, { + method: 'POST', + headers: Hellotext.headers, + body: JSON.stringify({ + token + }) + }); + return new Response(response.ok, response); + } + static async cancel(id, submissionId, token) { + const response = await fetch(`${this.endpoint}/${id}/submissions/${submissionId}/cancel`, { + method: 'POST', + headers: Hellotext.headers, + body: JSON.stringify({ + token + }) + }); + return new Response(response.ok, response); + } + static idempotencyKey() { + if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { + return crypto.randomUUID(); + } + return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; + } + static async fetchPopup(url) { + try { + return await fetch(url, { + method: 'GET', + headers: Hellotext.headers + }); + } catch (_) { + return { + ok: false + }; + } + } + static get runtimeDevice() { + if (Configuration.popup.device !== 'auto') return Configuration.popup.device; + return window.innerWidth <= 767 ? 'mobile' : 'desktop'; + } + static async parsePopupResponse(response) { + try { + return await response.json(); + } catch (_) { + return null; + } + } +} +export default PopupsAPI; \ No newline at end of file diff --git a/lib/controllers/popup_controller.cjs b/lib/controllers/popup_controller.cjs new file mode 100644 index 0000000..d6e6498 --- /dev/null +++ b/lib/controllers/popup_controller.cjs @@ -0,0 +1,811 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; +var _stimulus = require("@hotwired/stimulus"); +var _popups = _interopRequireDefault(require("../api/popups")); +var _hellotext = _interopRequireDefault(require("../hellotext")); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +/** + * An input rendered by the popup's server-side field components. + * + * @typedef {HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement} PopupInput + */ + +/** + * Identity used for completion copy and for locating the field to edit. + * A backend destination may have no matching input in the rendered form. + * + * @typedef {Object} PopupIdentity + * @property {PopupInput | undefined} input - Field associated with the destination. + * @property {'email' | 'phone'} kind - Field kind, distinct from the delivery channel. + * @property {string} value - Destination to display to the visitor. + */ + +/** + * Collected values retain both field lookup and their original step grouping. + * Checkbox values are booleans; other field values remain strings. + * + * @typedef {Object} PopupSubmissionPayload + * @property {string} [email] - Email input value for backend identity handling. + * @property {string} [phone] - Phone input value for backend identity handling. + * @property {Object} metadata + * @property {Object} metadata.capture - Capture metadata supplied by the server. + * @property {Object} metadata.fields - Values keyed by field identifier. + * @property {Array<{id: string, name: string, fields: Object}>} metadata.steps + */ + +/** + * A backend validation error, optionally associated with a built-in or custom field. + * + * @typedef {Object} PopupSubmissionError + * @property {string} [parameter] - Built-in field kind or custom property identifier. + * @property {string} [description] - Message suitable for displaying to the visitor. + */ +/** + * Controls a dashboard popup rendered by Popup::RuntimeComponent on merchant sites. + * + * The server owns the markup, styling, and initial hidden attributes: the bubble, + * dialog, later steps, and completion state arrive hidden. This controller chooses + * when to reveal them and manages the visitor's progress through the existing DOM. + * State initialized here belongs to one controller instance, not persistent storage. + * + * A successful submission opens the completion screen even when verification is + * pending. The backend owns delivery routing and verification; this controller + * displays the returned state and requests resends or cancellation using its token. + * + * Targets: + * - bubble: Launcher shown before the popup when bubble mode is enabled. + * - dialog: Popup dialog/surface wrapper. + * - step: Sequential form steps. + * - completed: Completion state shown after submission. + * - input: User-entered popup fields. + * - submitButton: Step buttons disabled while the submission is in flight. + * - globalError: Submission errors that cannot be shown beside an input. + * - resendButton: Delivery resend action and its localized countdown label. + * - changeDestinationButton: Action that returns to the delivered-to identity field. + * - deliveryCopy: Completion headline and description shown when a delivery is queued. + * - noDeliveryCopy: Server-rendered completion copy shown when no delivery is required. + * + * Values: + * - capture: Capture metadata supplied by the server and included in submissions. + * - device: Popup device targeting. + * - hasBubble: Whether the popup starts from a bubble. + * - id: Public popup identifier. + */ +class _default extends _stimulus.Controller { + static targets = ['bubble', 'dialog', 'step', 'completed', 'input', 'submitButton', 'globalError', 'resendButton', 'changeDestinationButton', 'deliveryCopy', 'noDeliveryCopy']; + static values = { + capture: Object, + device: String, + hasBubble: Boolean, + id: String + }; + + /** + * Establish progress and preserve the original resend label once per instance. + * Keeping this outside connect() avoids resetting progress or capturing the + * temporary countdown text when Stimulus reconnects the same controller. + * + * @returns {void} + */ + initialize() { + this.stepIndex = 0; + this.resendLabel = this.hasResendButtonTarget ? this.resendButtonTarget.textContent.trim() : ''; + } + + /** + * Announce that the popup has joined the DOM before applying the display policy. + * Mounting does not imply dialog visibility: the server supplies hidden markup, + * and device targeting or bubble mode may keep the dialog closed. + * + * @returns {void} + */ + connect() { + _hellotext.default.eventEmitter.dispatch('popup:mounted'); + this.evaluateDisplay(); + } + + /** + * Stop the countdown interval when detached so it does not keep updating old DOM. + * + * @returns {void} + */ + disconnect() { + this.stopResendCooldown(); + } + + /** + * Replace the launcher with the dialog inside an already eligible popup. + * Subscribers are notified when the dialog is revealed, not when the bubble appears. + * + * @param {Event} [event] - Optional launcher interaction whose default action is prevented. + * @returns {void} + */ + open(event) { + if (event) event.preventDefault(); + if (this.hasBubbleTarget) this.bubbleTarget.hidden = true; + this.dialogTarget.hidden = false; + _hellotext.default.eventEmitter.dispatch('popup:opened'); + } + + /** + * Dismiss the entire popup and remember that choice for this controller instance. + * Closing changes visibility; it does not cancel a submission or its delivery. + * + * @param {Event} [event] - Optional close-button interaction. + * @returns {void} + */ + close(event) { + if (event) event.preventDefault(); + this.dismissed = true; + this.dialogTarget.hidden = true; + if (this.hasBubbleTarget) this.bubbleTarget.hidden = true; + this.element.hidden = true; + _hellotext.default.eventEmitter.dispatch('popup:closed'); + } + + /** + * Validate the current step before advancing, or submit if this is the final step. + * Clear previous server validity errors first so corrected values can be checked. + * + * @param {Event} [event] - Optional step-button interaction. + * @returns {Promise} + */ + async next(event) { + if (event) event.preventDefault(); + this.clearCustomValidity(); + if (!this.currentStepValid()) { + this.showErrorMessages(this.currentStepInputs); + return; + } + this.clearErrorMessages(this.currentStepInputs); + if (this.stepIndex < this.stepTargets.length - 1) { + this.showStep(this.stepIndex + 1); + return; + } + await this.submit(); + } + + /** + * Send the collected steps only after the final step passes validation. + * Earlier form submissions act as Next, preserving the same progression for Enter + * and button clicks. Failures leave the form available for a deliberate retry. + * + * @param {Event} [event] - Optional form submission or final-button interaction. + * @returns {Promise} + */ + async submit(event) { + if (event) event.preventDefault(); + this.clearCustomValidity(); + if (this.stepIndex < this.stepTargets.length - 1) { + await this.next(); + return; + } + if (!this.currentStepValid()) { + this.showErrorMessages(this.currentStepInputs); + return; + } + this.clearErrorMessages(this.currentStepInputs); + this.clearGlobalError(); + this.submitButtonTargets.forEach(button => { + button.disabled = true; + }); + try { + const payload = this.submissionPayload(); + const response = await _popups.default.submit(this.idValue, payload, this.idempotencyKeyFor(payload)); + if (response.failed) { + await this.handleSubmissionError(response); + return; + } + + // Keep the backend's chosen route and action token together. Resend and edit + // must act on this accepted submission, even if a fallback route was selected. + const submission = await response.json(); + this.submissionId = submission.id; + this.submissionVerificationState = submission.verification_state; + this.submissionActionToken = submission.action_token; + this.submissionDeliveryStatus = submission.delivery_status; + this.submissionDeliveryChannel = submission.delivery_channel; + this.submissionDestination = submission.destination; + this.resetSubmissionRequest(); + } catch (_) { + // The server may have accepted a request whose response was lost. Retain the + // payload's idempotency key so another attempt can recover that submission. + this.showGlobalError(); + return; + } finally { + this.submitButtonTargets.forEach(button => { + button.disabled = false; + }); + } + this.showCompleted(); + } + + /** + * Apply dismissal and viewport eligibility before revealing any popup surface. + * Hide the root on rejection so this also works after a previously visible mount. + * + * @returns {void} + */ + evaluateDisplay() { + if (this.dismissed || !this.matchesDevice()) { + this.element.hidden = true; + return; + } + this.showInitialState(); + } + + /** + * Choose the launcher or immediate dialog without resetting entered form values. + * Set both surface states explicitly because a reconnect can reuse modified DOM. + * Bubble display alone does not emit the dialog's popup:opened event. + * + * @returns {void} + */ + showInitialState() { + this.element.hidden = false; + if (this.hasBubbleValue && this.hasBubbleTarget) { + this.bubbleTarget.hidden = false; + this.dialogTarget.hidden = true; + return; + } + if (this.hasBubbleTarget) this.bubbleTarget.hidden = true; + this.dialogTarget.hidden = false; + _hellotext.default.eventEmitter.dispatch('popup:opened'); + } + + /** + * Reveal one existing step and leave completion, preserving all collected values. + * Also used after confirmed cancellation to return to the destination's own step. + * + * @param {number} index - Zero-based index of a step in the rendered flow. + * @returns {void} + */ + showStep(index) { + this.stepIndex = index; + this.stepTargets.forEach((step, stepIndex) => { + step.hidden = stepIndex !== index; + }); + this.completedTarget.hidden = true; + } + + /** + * Replace the form steps with the result of an accepted submission. + * Completion reflects the response received so far; it does not assert that + * delivery or verification has finished, and it does not poll for later changes. + * + * @returns {void} + */ + showCompleted() { + this.stepTargets.forEach(step => { + step.hidden = true; + }); + this.interpolateCompletionCopy(); + this.configureCompletionActions(); + this.completedTarget.hidden = false; + } + + /** + * Fill destination/channel placeholders while preserving the server's rich markup. + * Replace text nodes from saved templates so visitor values stay text and a later + * corrected destination can replace the original placeholders again. + * + * @returns {void} + */ + interpolateCompletionCopy() { + const identity = this.completedIdentity; + if (!identity) return; + const replacements = { + destination: identity.value, + channel: this.submissionDeliveryChannel || identity.kind + }; + this.completionTextTemplates.forEach(({ + node, + template + }) => { + node.nodeValue = template.replace(/\{(destination|channel)\}/g, (placeholder, key) => replacements[key] || placeholder); + }); + } + + /** + * Format a local identity for completion copy when backend route data is absent. + * Phone prefixes and leading-zero removal apply only to this display fallback; + * submissionPayload() still sends the original field value. + * + * @param {PopupInput} input - Email or phone field containing a string value. + * @returns {string} Trimmed identity with the configured phone prefix when needed. + */ + identityValue(input) { + const value = this.inputValue(input).trim(); + if (input.dataset.popupFieldKind !== 'phone' || value.startsWith('+')) return value; + const prefix = input.dataset.popupPhonePrefix; + return prefix ? `${prefix}${value.replace(/^0+/, '')}` : value; + } + + /** + * Configure follow-up actions from the backend's delivery and verification state. + * Contact-only submissions show saved-details copy. Queued, unverified deliveries + * with an action token expose resend after the initial one-minute cooldown. + * + * @returns {void} + */ + configureCompletionActions() { + const deliveryRequired = this.submissionDeliveryStatus !== 'not_required'; + this.revealCompletionCopy(deliveryRequired); + if (!deliveryRequired) { + this.completedTarget.querySelector('[data-delivery-actions]')?.setAttribute('hidden', ''); + return; + } + const identity = this.completedIdentity; + if (!identity) return; + if (this.hasChangeDestinationButtonTarget) { + this.changeDestinationButtonTarget.textContent = this.changeDestinationButtonTarget.dataset[`${identity.kind}Label`]; + this.changeDestinationButtonTarget.hidden = false; + } + if (this.submissionId && this.submissionActionToken && this.submissionDeliveryStatus === 'queued' && this.submissionVerificationState === 'unverified' && this.hasResendButtonTarget) { + this.resendButtonTarget.hidden = false; + this.startResendCooldown(60); + } + } + + /** + * Request another delivery for the accepted submission using its action token. + * No edited destination is sent: the backend retains ownership of the route. + * Ignore repeated clicks while pending or cooling down; honor Retry-After on + * success or rate limiting, and allow a manual retry after other failures. + * + * @param {Event} [event] - Optional resend-button interaction. + * @returns {Promise} + */ + async resend(event) { + if (event) event.preventDefault(); + if (!this.submissionId || !this.submissionActionToken || this.resendPending || this.resendCooldownActive) return; + const identity = this.completedIdentity; + if (!identity) return; + this.resendPending = true; + this.resendButtonTarget.disabled = true; + try { + const response = await _popups.default.resend(this.idValue, this.submissionId, this.submissionActionToken); + const retryAfter = Number(response.data.headers?.get('Retry-After')) || 60; + if (response.succeeded || response.data.status === 429) { + this.startResendCooldown(retryAfter); + } else { + this.resendButtonTarget.disabled = false; + } + } catch (_) { + this.resendButtonTarget.disabled = false; + } finally { + this.resendPending = false; + } + } + + /** + * Cancel the accepted submission before allowing its destination to be edited. + * Returning to the form before confirmation could create a replacement while the + * previous submission remains deliverable. On failure, keep its state and the + * completion screen; on success, focus the field matching the backend's route. + * + * @param {Event} [event] - Optional change-email or change-phone interaction. + * @returns {Promise} + */ + async changeDestination(event) { + if (event) event.preventDefault(); + if (!this.submissionId || !this.submissionActionToken || this.changeDestinationPending) return; + const input = this.completedIdentity?.input; + if (!input) return; + const stepIndex = this.stepTargets.findIndex(step => step.dataset.stepId === input.dataset.popupStepId); + if (stepIndex < 0) return; + this.changeDestinationPending = true; + this.changeDestinationButtonTarget.disabled = true; + try { + const response = await _popups.default.cancel(this.idValue, this.submissionId, this.submissionActionToken); + if (response.failed) return; + this.stopResendCooldown(); + this.submissionId = null; + this.submissionActionToken = null; + this.submissionVerificationState = null; + this.submissionDeliveryStatus = null; + this.submissionDeliveryChannel = null; + this.submissionDestination = null; + this.resetSubmissionRequest(); + this.showStep(stepIndex); + input.focus(); + } catch (_) { + // Keep Completed visible when cancellation cannot be confirmed. Starting + // a replacement submission before that boundary could deliver twice. + } finally { + this.changeDestinationPending = false; + this.changeDestinationButtonTarget.disabled = false; + } + } + + /** + * Replace any countdown and immediately reflect its remaining time in the button. + * Store a deadline rather than decrementing a counter so delayed timer callbacks + * do not lengthen the cooldown when the browser throttles background tabs. + * + * @param {number} seconds - Cooldown duration, clamped to at least one second. + * @returns {void} + */ + startResendCooldown(seconds) { + this.stopResendCooldown(); + this.resendCooldownEndsAt = Date.now() + Math.max(seconds, 1) * 1000; + this.updateResendCountdown(); + this.resendTimer = window.setInterval(() => this.updateResendCountdown(), 1000); + } + + /** + * Clear the timer and deadline. Callers own the next button or screen state; + * stopping a timer during disconnect or cancellation must not reveal UI itself. + * + * @returns {void} + */ + stopResendCooldown() { + if (this.resendTimer) window.clearInterval(this.resendTimer); + this.resendTimer = null; + this.resendCooldownEndsAt = null; + } + + /** + * Render the localized remaining time, or restore the original label on expiry. + * Recompute from the deadline on each tick instead of assuming ticks are punctual. + * + * @returns {void} + */ + updateResendCountdown() { + const seconds = Math.max(0, Math.ceil((this.resendCooldownEndsAt - Date.now()) / 1000)); + if (seconds === 0) { + this.stopResendCooldown(); + this.resendButtonTarget.textContent = this.resendLabel; + this.resendButtonTarget.disabled = false; + return; + } + const time = `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`; + const template = this.resendButtonTarget.dataset.countdownLabel || `${this.resendLabel} %{time}`; + this.resendButtonTarget.textContent = template.replace('%{time}', time); + this.resendButtonTarget.disabled = true; + } + + /** + * Check the deadline independently of whether the latest timer tick has run. + * + * @returns {boolean} Whether a resend is still blocked by the local cooldown. + */ + get resendCooldownActive() { + return this.resendCooldownEndsAt > Date.now(); + } + + /** + * Choose a populated local identity when no backend destination is available. + * Required fields take precedence; optional identities are a fallback. + * + * @returns {PopupIdentity | undefined} First populated identity in priority order. + */ + get completionIdentity() { + return this.identityInputs.map(input => ({ + input, + kind: input.dataset.popupFieldKind, + value: this.identityValue(input) + })).find(({ + value + }) => value); + } + + /** + * Prefer the backend's actual destination so fallback delivery is represented + * accurately. Map non-email delivery channels, such as SMS or WhatsApp, back to + * the phone field for editing; retain the actual channel separately for copy. + * + * @returns {PopupIdentity | undefined} Backend identity or the local display fallback. + */ + get completedIdentity() { + if (this.submissionDestination && this.submissionDeliveryChannel) { + const kind = this.submissionDeliveryChannel === 'email' ? 'email' : 'phone'; + const input = this.identityInputs.find(candidate => candidate.dataset.popupFieldKind === kind); + return { + input, + kind, + value: this.submissionDestination + }; + } + return this.completionIdentity; + } + + /** + * Reveal the completion copy that matches the delivery outcome. The server renders both + * variants and owns their markup; the controller only chooses which one is visible, so + * no completion structure is built here and interpolated text nodes are never replaced. + * + * @param {boolean} deliveryRequired - Whether the submission queued a delivery. + * @returns {void} + */ + revealCompletionCopy(deliveryRequired) { + if (this.hasDeliveryCopyTarget) { + this.deliveryCopyTargets.forEach(element => { + element.hidden = !deliveryRequired; + }); + } + if (this.hasNoDeliveryCopyTarget) { + this.noDeliveryCopyTargets.forEach(element => { + element.hidden = deliveryRequired; + }); + } + } + + /** + * Apply the browser's constraints only to the step the visitor is completing. + * Required fields in later, hidden steps must not block earlier progression. + * + * @returns {boolean} Whether every input associated with the current step is valid. + */ + currentStepValid() { + return this.currentStepInputs.every(input => input.checkValidity()); + } + + /** + * Mirror native/custom validity messages into the server's inline error containers. + * Valid fields clear their old message; fields without a container are skipped. + * + * @param {PopupInput[]} inputs - Fields whose current validity should be displayed. + * @returns {void} + */ + showErrorMessages(inputs) { + inputs.forEach(input => { + const container = input.closest('.hellotext--popup-field')?.querySelector('[data-error-container]'); + if (!container) return; + container.textContent = input.validity.valid ? '' : input.validationMessage; + }); + } + + /** + * Remove displayed field errors without changing values or validity constraints. + * + * @param {PopupInput[]} [inputs=this.inputTargets] - Fields to clear, defaulting to all. + * @returns {void} + */ + clearErrorMessages(inputs = this.inputTargets) { + inputs.forEach(input => { + const container = input.closest('.hellotext--popup-field')?.querySelector('[data-error-container]'); + if (container) container.textContent = ''; + }); + } + + /** + * Remove server-set validity messages before validating a fresh attempt. + * Native constraints remain active; stale custom errors must not reject edits. + * + * @returns {void} + */ + clearCustomValidity() { + this.inputTargets.forEach(input => input.setCustomValidity('')); + } + + /** + * Clear and hide the optional form-level error before a new submission attempt. + * + * @returns {void} + */ + clearGlobalError() { + if (!this.hasGlobalErrorTarget) return; + this.globalErrorTarget.textContent = ''; + this.globalErrorTarget.hidden = true; + } + + /** + * Show a form-level failure with the server's localized fallback when needed. + * Render messages as text, and tolerate markup without a global-error target. + * + * @param {string | null} [message=null] - Specific error, or no value for fallback copy. + * @returns {void} + */ + showGlobalError(message = null) { + if (!this.hasGlobalErrorTarget) return; + this.globalErrorTarget.textContent = message || this.globalErrorTarget.dataset.submitError || 'Unable to submit. Please try again.'; + this.globalErrorTarget.hidden = false; + } + + /** + * Route backend errors to matching fields or the form-level error container. + * Unreadable JSON or an empty errors list uses generic copy when the server + * cannot provide a structured validation explanation. + * + * @param {import('../api/response').Response} response - Failed submission response. + * @returns {Promise} + */ + async handleSubmissionError(response) { + let data; + try { + data = await response.json(); + } catch (_) { + this.showGlobalError(); + return; + } + const errors = data.errors || []; + const generalErrors = []; + errors.forEach(error => { + const input = this.inputForError(error); + if (!input) { + if (error.description) generalErrors.push(error.description); + return; + } + input.setCustomValidity(error.description || input.validationMessage); + input.reportValidity(); + }); + this.showErrorMessages(this.inputTargets); + if (generalErrors.length) this.showGlobalError(generalErrors.join(' '));else if (!errors.length) this.showGlobalError(); + } + + /** + * Match both built-in identity names and custom property keys in backend errors. + * Missing or unmatched parameters belong to the form-level error path. + * + * @param {PopupSubmissionError} error - Error identifying a field when possible. + * @returns {PopupInput | null | undefined} Matching input, or no match. + */ + inputForError(error) { + const parameter = error.parameter; + if (!parameter) return null; + return this.inputTargets.find(input => { + return input.dataset.popupFieldKind === parameter || input.dataset.popupFieldKey === parameter; + }); + } + + /** + * Collect the whole flow while preserving the dashboard's field and step identity. + * Top-level email/phone support backend identity handling; metadata retains all + * values, including custom properties and checkboxes, with their step context. + * + * @returns {PopupSubmissionPayload} Collected data before the API adds session context. + */ + submissionPayload() { + const payload = { + metadata: { + capture: this.captureValue || {}, + fields: {}, + steps: [] + } + }; + this.stepTargets.forEach(step => { + const stepFields = {}; + const inputs = this.inputsForStep(step); + inputs.forEach(input => { + const value = this.inputValue(input); + const key = input.dataset.popupFieldKey || input.name; + stepFields[key] = value; + payload.metadata.fields[key] = value; + if (input.dataset.popupFieldKind === 'email') payload.email = value; + if (input.dataset.popupFieldKind === 'phone') payload.phone = value; + }); + payload.metadata.steps.push({ + id: step.dataset.stepId, + name: step.dataset.stepName, + fields: stepFields + }); + }); + return payload; + } + + /** + * Reuse the request key while the serialized payload remains unchanged. + * A failed or unreadable response does not prove the submission was rejected; + * retaining the key lets a manual retry recover the same server-side operation. + * Changed values represent a new attempt and receive a fresh key. + * + * @param {PopupSubmissionPayload} payload - Data about to be submitted. + * @returns {string} Key associated with this controller's current payload snapshot. + */ + idempotencyKeyFor(payload) { + const serializedPayload = JSON.stringify(payload); + if (this.submissionPayloadSnapshot !== serializedPayload) { + this.submissionPayloadSnapshot = serializedPayload; + this.submissionIdempotencyKey = _popups.default.idempotencyKey(); + } + return this.submissionIdempotencyKey; + } + + /** + * Forget the retry identity after a parsed success or confirmed cancellation. + * Failures intentionally keep it, because the backend may already have accepted + * the request even though the visitor has not received its response. + * + * @returns {void} + */ + resetSubmissionRequest() { + this.submissionPayloadSnapshot = null; + this.submissionIdempotencyKey = null; + } + + /** + * Preserve checkbox choices as booleans and other values as entered strings. + * Reading checkbox.value would lose whether the visitor actually checked it. + * + * @param {PopupInput} input - Field to read without mutating its value. + * @returns {string | boolean} Submitted representation of the field's current value. + */ + inputValue(input) { + if (input.type === 'checkbox') return input.checked; + return input.value; + } + + /** + * Associate inputs through the server's step IDs rather than DOM nesting. + * Layout wrappers can change without changing validation or payload grouping. + * + * @param {HTMLElement} step - Step carrying a data-step-id attribute. + * @returns {PopupInput[]} Inputs whose data-popup-step-id matches this step. + */ + inputsForStep(step) { + return this.inputTargets.filter(input => input.dataset.popupStepId === step.dataset.stepId); + } + + /** + * Prioritize required email/phone fields for local completion identity selection. + * Preserve DOM order within the required and optional groups. + * + * @returns {PopupInput[]} Identity fields ordered by required status, then DOM order. + */ + get identityInputs() { + const inputs = this.inputTargets.filter(input => { + return ['email', 'phone'].includes(input.dataset.popupFieldKind); + }); + return inputs.filter(input => input.required).concat(inputs.filter(input => !input.required)); + } + + /** + * Snapshot completion text nodes before the first placeholder replacement. + * Reusing the original templates supports a corrected destination on a later + * submission while preserving surrounding markup and existing DOM references. + * + * @returns {Array<{node: Text, template: string}>} Cached nodes and their original text. + */ + get completionTextTemplates() { + if (this._completionTextTemplates) return this._completionTextTemplates; + const walker = document.createTreeWalker(this.completedTarget, NodeFilter.SHOW_TEXT); + this._completionTextTemplates = []; + while (walker.nextNode()) { + this._completionTextTemplates.push({ + node: walker.currentNode, + template: walker.currentNode.nodeValue + }); + } + return this._completionTextTemplates; + } + + /** + * Evaluate the dashboard's device target against the current viewport. + * The 768px split matches the API's automatic device selection; all or unspecified + * targets are unrestricted. This check runs when called, not on a resize listener. + * + * @returns {boolean} Whether this viewport is eligible to display the popup. + */ + matchesDevice() { + if (this.deviceValue === 'all') return true; + if (this.deviceValue === 'mobile') return window.innerWidth < 768; + if (this.deviceValue === 'desktop') return window.innerWidth >= 768; + return true; + } + + /** + * Resolve the active step from the server-rendered sequence and local progress. + * + * @returns {HTMLElement | undefined} Step at the current index, if present. + */ + get currentStep() { + return this.stepTargets[this.stepIndex]; + } + + /** + * Select the active step's fields for progression validation and inline errors. + * Requires a current step; the server renders this controller only for a flow + * with steps, and navigation selects indices from that rendered sequence. + * + * @returns {PopupInput[]} Fields associated with the current step. + */ + get currentStepInputs() { + return this.inputsForStep(this.currentStep); + } +} +exports.default = _default; \ No newline at end of file diff --git a/lib/controllers/popup_controller.js b/lib/controllers/popup_controller.js new file mode 100644 index 0000000..f356c94 --- /dev/null +++ b/lib/controllers/popup_controller.js @@ -0,0 +1,805 @@ +import { Controller } from '@hotwired/stimulus'; +import PopupsAPI from '../api/popups'; +import Hellotext from '../hellotext'; + +/** + * An input rendered by the popup's server-side field components. + * + * @typedef {HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement} PopupInput + */ + +/** + * Identity used for completion copy and for locating the field to edit. + * A backend destination may have no matching input in the rendered form. + * + * @typedef {Object} PopupIdentity + * @property {PopupInput | undefined} input - Field associated with the destination. + * @property {'email' | 'phone'} kind - Field kind, distinct from the delivery channel. + * @property {string} value - Destination to display to the visitor. + */ + +/** + * Collected values retain both field lookup and their original step grouping. + * Checkbox values are booleans; other field values remain strings. + * + * @typedef {Object} PopupSubmissionPayload + * @property {string} [email] - Email input value for backend identity handling. + * @property {string} [phone] - Phone input value for backend identity handling. + * @property {Object} metadata + * @property {Object} metadata.capture - Capture metadata supplied by the server. + * @property {Object} metadata.fields - Values keyed by field identifier. + * @property {Array<{id: string, name: string, fields: Object}>} metadata.steps + */ + +/** + * A backend validation error, optionally associated with a built-in or custom field. + * + * @typedef {Object} PopupSubmissionError + * @property {string} [parameter] - Built-in field kind or custom property identifier. + * @property {string} [description] - Message suitable for displaying to the visitor. + */ + +/** + * Controls a dashboard popup rendered by Popup::RuntimeComponent on merchant sites. + * + * The server owns the markup, styling, and initial hidden attributes: the bubble, + * dialog, later steps, and completion state arrive hidden. This controller chooses + * when to reveal them and manages the visitor's progress through the existing DOM. + * State initialized here belongs to one controller instance, not persistent storage. + * + * A successful submission opens the completion screen even when verification is + * pending. The backend owns delivery routing and verification; this controller + * displays the returned state and requests resends or cancellation using its token. + * + * Targets: + * - bubble: Launcher shown before the popup when bubble mode is enabled. + * - dialog: Popup dialog/surface wrapper. + * - step: Sequential form steps. + * - completed: Completion state shown after submission. + * - input: User-entered popup fields. + * - submitButton: Step buttons disabled while the submission is in flight. + * - globalError: Submission errors that cannot be shown beside an input. + * - resendButton: Delivery resend action and its localized countdown label. + * - changeDestinationButton: Action that returns to the delivered-to identity field. + * - deliveryCopy: Completion headline and description shown when a delivery is queued. + * - noDeliveryCopy: Server-rendered completion copy shown when no delivery is required. + * + * Values: + * - capture: Capture metadata supplied by the server and included in submissions. + * - device: Popup device targeting. + * - hasBubble: Whether the popup starts from a bubble. + * - id: Public popup identifier. + */ +export default class extends Controller { + static targets = ['bubble', 'dialog', 'step', 'completed', 'input', 'submitButton', 'globalError', 'resendButton', 'changeDestinationButton', 'deliveryCopy', 'noDeliveryCopy']; + static values = { + capture: Object, + device: String, + hasBubble: Boolean, + id: String + }; + + /** + * Establish progress and preserve the original resend label once per instance. + * Keeping this outside connect() avoids resetting progress or capturing the + * temporary countdown text when Stimulus reconnects the same controller. + * + * @returns {void} + */ + initialize() { + this.stepIndex = 0; + this.resendLabel = this.hasResendButtonTarget ? this.resendButtonTarget.textContent.trim() : ''; + } + + /** + * Announce that the popup has joined the DOM before applying the display policy. + * Mounting does not imply dialog visibility: the server supplies hidden markup, + * and device targeting or bubble mode may keep the dialog closed. + * + * @returns {void} + */ + connect() { + Hellotext.eventEmitter.dispatch('popup:mounted'); + this.evaluateDisplay(); + } + + /** + * Stop the countdown interval when detached so it does not keep updating old DOM. + * + * @returns {void} + */ + disconnect() { + this.stopResendCooldown(); + } + + /** + * Replace the launcher with the dialog inside an already eligible popup. + * Subscribers are notified when the dialog is revealed, not when the bubble appears. + * + * @param {Event} [event] - Optional launcher interaction whose default action is prevented. + * @returns {void} + */ + open(event) { + if (event) event.preventDefault(); + if (this.hasBubbleTarget) this.bubbleTarget.hidden = true; + this.dialogTarget.hidden = false; + Hellotext.eventEmitter.dispatch('popup:opened'); + } + + /** + * Dismiss the entire popup and remember that choice for this controller instance. + * Closing changes visibility; it does not cancel a submission or its delivery. + * + * @param {Event} [event] - Optional close-button interaction. + * @returns {void} + */ + close(event) { + if (event) event.preventDefault(); + this.dismissed = true; + this.dialogTarget.hidden = true; + if (this.hasBubbleTarget) this.bubbleTarget.hidden = true; + this.element.hidden = true; + Hellotext.eventEmitter.dispatch('popup:closed'); + } + + /** + * Validate the current step before advancing, or submit if this is the final step. + * Clear previous server validity errors first so corrected values can be checked. + * + * @param {Event} [event] - Optional step-button interaction. + * @returns {Promise} + */ + async next(event) { + if (event) event.preventDefault(); + this.clearCustomValidity(); + if (!this.currentStepValid()) { + this.showErrorMessages(this.currentStepInputs); + return; + } + this.clearErrorMessages(this.currentStepInputs); + if (this.stepIndex < this.stepTargets.length - 1) { + this.showStep(this.stepIndex + 1); + return; + } + await this.submit(); + } + + /** + * Send the collected steps only after the final step passes validation. + * Earlier form submissions act as Next, preserving the same progression for Enter + * and button clicks. Failures leave the form available for a deliberate retry. + * + * @param {Event} [event] - Optional form submission or final-button interaction. + * @returns {Promise} + */ + async submit(event) { + if (event) event.preventDefault(); + this.clearCustomValidity(); + if (this.stepIndex < this.stepTargets.length - 1) { + await this.next(); + return; + } + if (!this.currentStepValid()) { + this.showErrorMessages(this.currentStepInputs); + return; + } + this.clearErrorMessages(this.currentStepInputs); + this.clearGlobalError(); + this.submitButtonTargets.forEach(button => { + button.disabled = true; + }); + try { + const payload = this.submissionPayload(); + const response = await PopupsAPI.submit(this.idValue, payload, this.idempotencyKeyFor(payload)); + if (response.failed) { + await this.handleSubmissionError(response); + return; + } + + // Keep the backend's chosen route and action token together. Resend and edit + // must act on this accepted submission, even if a fallback route was selected. + const submission = await response.json(); + this.submissionId = submission.id; + this.submissionVerificationState = submission.verification_state; + this.submissionActionToken = submission.action_token; + this.submissionDeliveryStatus = submission.delivery_status; + this.submissionDeliveryChannel = submission.delivery_channel; + this.submissionDestination = submission.destination; + this.resetSubmissionRequest(); + } catch (_) { + // The server may have accepted a request whose response was lost. Retain the + // payload's idempotency key so another attempt can recover that submission. + this.showGlobalError(); + return; + } finally { + this.submitButtonTargets.forEach(button => { + button.disabled = false; + }); + } + this.showCompleted(); + } + + /** + * Apply dismissal and viewport eligibility before revealing any popup surface. + * Hide the root on rejection so this also works after a previously visible mount. + * + * @returns {void} + */ + evaluateDisplay() { + if (this.dismissed || !this.matchesDevice()) { + this.element.hidden = true; + return; + } + this.showInitialState(); + } + + /** + * Choose the launcher or immediate dialog without resetting entered form values. + * Set both surface states explicitly because a reconnect can reuse modified DOM. + * Bubble display alone does not emit the dialog's popup:opened event. + * + * @returns {void} + */ + showInitialState() { + this.element.hidden = false; + if (this.hasBubbleValue && this.hasBubbleTarget) { + this.bubbleTarget.hidden = false; + this.dialogTarget.hidden = true; + return; + } + if (this.hasBubbleTarget) this.bubbleTarget.hidden = true; + this.dialogTarget.hidden = false; + Hellotext.eventEmitter.dispatch('popup:opened'); + } + + /** + * Reveal one existing step and leave completion, preserving all collected values. + * Also used after confirmed cancellation to return to the destination's own step. + * + * @param {number} index - Zero-based index of a step in the rendered flow. + * @returns {void} + */ + showStep(index) { + this.stepIndex = index; + this.stepTargets.forEach((step, stepIndex) => { + step.hidden = stepIndex !== index; + }); + this.completedTarget.hidden = true; + } + + /** + * Replace the form steps with the result of an accepted submission. + * Completion reflects the response received so far; it does not assert that + * delivery or verification has finished, and it does not poll for later changes. + * + * @returns {void} + */ + showCompleted() { + this.stepTargets.forEach(step => { + step.hidden = true; + }); + this.interpolateCompletionCopy(); + this.configureCompletionActions(); + this.completedTarget.hidden = false; + } + + /** + * Fill destination/channel placeholders while preserving the server's rich markup. + * Replace text nodes from saved templates so visitor values stay text and a later + * corrected destination can replace the original placeholders again. + * + * @returns {void} + */ + interpolateCompletionCopy() { + const identity = this.completedIdentity; + if (!identity) return; + const replacements = { + destination: identity.value, + channel: this.submissionDeliveryChannel || identity.kind + }; + this.completionTextTemplates.forEach(({ + node, + template + }) => { + node.nodeValue = template.replace(/\{(destination|channel)\}/g, (placeholder, key) => replacements[key] || placeholder); + }); + } + + /** + * Format a local identity for completion copy when backend route data is absent. + * Phone prefixes and leading-zero removal apply only to this display fallback; + * submissionPayload() still sends the original field value. + * + * @param {PopupInput} input - Email or phone field containing a string value. + * @returns {string} Trimmed identity with the configured phone prefix when needed. + */ + identityValue(input) { + const value = this.inputValue(input).trim(); + if (input.dataset.popupFieldKind !== 'phone' || value.startsWith('+')) return value; + const prefix = input.dataset.popupPhonePrefix; + return prefix ? `${prefix}${value.replace(/^0+/, '')}` : value; + } + + /** + * Configure follow-up actions from the backend's delivery and verification state. + * Contact-only submissions show saved-details copy. Queued, unverified deliveries + * with an action token expose resend after the initial one-minute cooldown. + * + * @returns {void} + */ + configureCompletionActions() { + const deliveryRequired = this.submissionDeliveryStatus !== 'not_required'; + this.revealCompletionCopy(deliveryRequired); + if (!deliveryRequired) { + this.completedTarget.querySelector('[data-delivery-actions]')?.setAttribute('hidden', ''); + return; + } + const identity = this.completedIdentity; + if (!identity) return; + if (this.hasChangeDestinationButtonTarget) { + this.changeDestinationButtonTarget.textContent = this.changeDestinationButtonTarget.dataset[`${identity.kind}Label`]; + this.changeDestinationButtonTarget.hidden = false; + } + if (this.submissionId && this.submissionActionToken && this.submissionDeliveryStatus === 'queued' && this.submissionVerificationState === 'unverified' && this.hasResendButtonTarget) { + this.resendButtonTarget.hidden = false; + this.startResendCooldown(60); + } + } + + /** + * Request another delivery for the accepted submission using its action token. + * No edited destination is sent: the backend retains ownership of the route. + * Ignore repeated clicks while pending or cooling down; honor Retry-After on + * success or rate limiting, and allow a manual retry after other failures. + * + * @param {Event} [event] - Optional resend-button interaction. + * @returns {Promise} + */ + async resend(event) { + if (event) event.preventDefault(); + if (!this.submissionId || !this.submissionActionToken || this.resendPending || this.resendCooldownActive) return; + const identity = this.completedIdentity; + if (!identity) return; + this.resendPending = true; + this.resendButtonTarget.disabled = true; + try { + const response = await PopupsAPI.resend(this.idValue, this.submissionId, this.submissionActionToken); + const retryAfter = Number(response.data.headers?.get('Retry-After')) || 60; + if (response.succeeded || response.data.status === 429) { + this.startResendCooldown(retryAfter); + } else { + this.resendButtonTarget.disabled = false; + } + } catch (_) { + this.resendButtonTarget.disabled = false; + } finally { + this.resendPending = false; + } + } + + /** + * Cancel the accepted submission before allowing its destination to be edited. + * Returning to the form before confirmation could create a replacement while the + * previous submission remains deliverable. On failure, keep its state and the + * completion screen; on success, focus the field matching the backend's route. + * + * @param {Event} [event] - Optional change-email or change-phone interaction. + * @returns {Promise} + */ + async changeDestination(event) { + if (event) event.preventDefault(); + if (!this.submissionId || !this.submissionActionToken || this.changeDestinationPending) return; + const input = this.completedIdentity?.input; + if (!input) return; + const stepIndex = this.stepTargets.findIndex(step => step.dataset.stepId === input.dataset.popupStepId); + if (stepIndex < 0) return; + this.changeDestinationPending = true; + this.changeDestinationButtonTarget.disabled = true; + try { + const response = await PopupsAPI.cancel(this.idValue, this.submissionId, this.submissionActionToken); + if (response.failed) return; + this.stopResendCooldown(); + this.submissionId = null; + this.submissionActionToken = null; + this.submissionVerificationState = null; + this.submissionDeliveryStatus = null; + this.submissionDeliveryChannel = null; + this.submissionDestination = null; + this.resetSubmissionRequest(); + this.showStep(stepIndex); + input.focus(); + } catch (_) { + // Keep Completed visible when cancellation cannot be confirmed. Starting + // a replacement submission before that boundary could deliver twice. + } finally { + this.changeDestinationPending = false; + this.changeDestinationButtonTarget.disabled = false; + } + } + + /** + * Replace any countdown and immediately reflect its remaining time in the button. + * Store a deadline rather than decrementing a counter so delayed timer callbacks + * do not lengthen the cooldown when the browser throttles background tabs. + * + * @param {number} seconds - Cooldown duration, clamped to at least one second. + * @returns {void} + */ + startResendCooldown(seconds) { + this.stopResendCooldown(); + this.resendCooldownEndsAt = Date.now() + Math.max(seconds, 1) * 1000; + this.updateResendCountdown(); + this.resendTimer = window.setInterval(() => this.updateResendCountdown(), 1000); + } + + /** + * Clear the timer and deadline. Callers own the next button or screen state; + * stopping a timer during disconnect or cancellation must not reveal UI itself. + * + * @returns {void} + */ + stopResendCooldown() { + if (this.resendTimer) window.clearInterval(this.resendTimer); + this.resendTimer = null; + this.resendCooldownEndsAt = null; + } + + /** + * Render the localized remaining time, or restore the original label on expiry. + * Recompute from the deadline on each tick instead of assuming ticks are punctual. + * + * @returns {void} + */ + updateResendCountdown() { + const seconds = Math.max(0, Math.ceil((this.resendCooldownEndsAt - Date.now()) / 1000)); + if (seconds === 0) { + this.stopResendCooldown(); + this.resendButtonTarget.textContent = this.resendLabel; + this.resendButtonTarget.disabled = false; + return; + } + const time = `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`; + const template = this.resendButtonTarget.dataset.countdownLabel || `${this.resendLabel} %{time}`; + this.resendButtonTarget.textContent = template.replace('%{time}', time); + this.resendButtonTarget.disabled = true; + } + + /** + * Check the deadline independently of whether the latest timer tick has run. + * + * @returns {boolean} Whether a resend is still blocked by the local cooldown. + */ + get resendCooldownActive() { + return this.resendCooldownEndsAt > Date.now(); + } + + /** + * Choose a populated local identity when no backend destination is available. + * Required fields take precedence; optional identities are a fallback. + * + * @returns {PopupIdentity | undefined} First populated identity in priority order. + */ + get completionIdentity() { + return this.identityInputs.map(input => ({ + input, + kind: input.dataset.popupFieldKind, + value: this.identityValue(input) + })).find(({ + value + }) => value); + } + + /** + * Prefer the backend's actual destination so fallback delivery is represented + * accurately. Map non-email delivery channels, such as SMS or WhatsApp, back to + * the phone field for editing; retain the actual channel separately for copy. + * + * @returns {PopupIdentity | undefined} Backend identity or the local display fallback. + */ + get completedIdentity() { + if (this.submissionDestination && this.submissionDeliveryChannel) { + const kind = this.submissionDeliveryChannel === 'email' ? 'email' : 'phone'; + const input = this.identityInputs.find(candidate => candidate.dataset.popupFieldKind === kind); + return { + input, + kind, + value: this.submissionDestination + }; + } + return this.completionIdentity; + } + + /** + * Reveal the completion copy that matches the delivery outcome. The server renders both + * variants and owns their markup; the controller only chooses which one is visible, so + * no completion structure is built here and interpolated text nodes are never replaced. + * + * @param {boolean} deliveryRequired - Whether the submission queued a delivery. + * @returns {void} + */ + revealCompletionCopy(deliveryRequired) { + if (this.hasDeliveryCopyTarget) { + this.deliveryCopyTargets.forEach(element => { + element.hidden = !deliveryRequired; + }); + } + if (this.hasNoDeliveryCopyTarget) { + this.noDeliveryCopyTargets.forEach(element => { + element.hidden = deliveryRequired; + }); + } + } + + /** + * Apply the browser's constraints only to the step the visitor is completing. + * Required fields in later, hidden steps must not block earlier progression. + * + * @returns {boolean} Whether every input associated with the current step is valid. + */ + currentStepValid() { + return this.currentStepInputs.every(input => input.checkValidity()); + } + + /** + * Mirror native/custom validity messages into the server's inline error containers. + * Valid fields clear their old message; fields without a container are skipped. + * + * @param {PopupInput[]} inputs - Fields whose current validity should be displayed. + * @returns {void} + */ + showErrorMessages(inputs) { + inputs.forEach(input => { + const container = input.closest('.hellotext--popup-field')?.querySelector('[data-error-container]'); + if (!container) return; + container.textContent = input.validity.valid ? '' : input.validationMessage; + }); + } + + /** + * Remove displayed field errors without changing values or validity constraints. + * + * @param {PopupInput[]} [inputs=this.inputTargets] - Fields to clear, defaulting to all. + * @returns {void} + */ + clearErrorMessages(inputs = this.inputTargets) { + inputs.forEach(input => { + const container = input.closest('.hellotext--popup-field')?.querySelector('[data-error-container]'); + if (container) container.textContent = ''; + }); + } + + /** + * Remove server-set validity messages before validating a fresh attempt. + * Native constraints remain active; stale custom errors must not reject edits. + * + * @returns {void} + */ + clearCustomValidity() { + this.inputTargets.forEach(input => input.setCustomValidity('')); + } + + /** + * Clear and hide the optional form-level error before a new submission attempt. + * + * @returns {void} + */ + clearGlobalError() { + if (!this.hasGlobalErrorTarget) return; + this.globalErrorTarget.textContent = ''; + this.globalErrorTarget.hidden = true; + } + + /** + * Show a form-level failure with the server's localized fallback when needed. + * Render messages as text, and tolerate markup without a global-error target. + * + * @param {string | null} [message=null] - Specific error, or no value for fallback copy. + * @returns {void} + */ + showGlobalError(message = null) { + if (!this.hasGlobalErrorTarget) return; + this.globalErrorTarget.textContent = message || this.globalErrorTarget.dataset.submitError || 'Unable to submit. Please try again.'; + this.globalErrorTarget.hidden = false; + } + + /** + * Route backend errors to matching fields or the form-level error container. + * Unreadable JSON or an empty errors list uses generic copy when the server + * cannot provide a structured validation explanation. + * + * @param {import('../api/response').Response} response - Failed submission response. + * @returns {Promise} + */ + async handleSubmissionError(response) { + let data; + try { + data = await response.json(); + } catch (_) { + this.showGlobalError(); + return; + } + const errors = data.errors || []; + const generalErrors = []; + errors.forEach(error => { + const input = this.inputForError(error); + if (!input) { + if (error.description) generalErrors.push(error.description); + return; + } + input.setCustomValidity(error.description || input.validationMessage); + input.reportValidity(); + }); + this.showErrorMessages(this.inputTargets); + if (generalErrors.length) this.showGlobalError(generalErrors.join(' '));else if (!errors.length) this.showGlobalError(); + } + + /** + * Match both built-in identity names and custom property keys in backend errors. + * Missing or unmatched parameters belong to the form-level error path. + * + * @param {PopupSubmissionError} error - Error identifying a field when possible. + * @returns {PopupInput | null | undefined} Matching input, or no match. + */ + inputForError(error) { + const parameter = error.parameter; + if (!parameter) return null; + return this.inputTargets.find(input => { + return input.dataset.popupFieldKind === parameter || input.dataset.popupFieldKey === parameter; + }); + } + + /** + * Collect the whole flow while preserving the dashboard's field and step identity. + * Top-level email/phone support backend identity handling; metadata retains all + * values, including custom properties and checkboxes, with their step context. + * + * @returns {PopupSubmissionPayload} Collected data before the API adds session context. + */ + submissionPayload() { + const payload = { + metadata: { + capture: this.captureValue || {}, + fields: {}, + steps: [] + } + }; + this.stepTargets.forEach(step => { + const stepFields = {}; + const inputs = this.inputsForStep(step); + inputs.forEach(input => { + const value = this.inputValue(input); + const key = input.dataset.popupFieldKey || input.name; + stepFields[key] = value; + payload.metadata.fields[key] = value; + if (input.dataset.popupFieldKind === 'email') payload.email = value; + if (input.dataset.popupFieldKind === 'phone') payload.phone = value; + }); + payload.metadata.steps.push({ + id: step.dataset.stepId, + name: step.dataset.stepName, + fields: stepFields + }); + }); + return payload; + } + + /** + * Reuse the request key while the serialized payload remains unchanged. + * A failed or unreadable response does not prove the submission was rejected; + * retaining the key lets a manual retry recover the same server-side operation. + * Changed values represent a new attempt and receive a fresh key. + * + * @param {PopupSubmissionPayload} payload - Data about to be submitted. + * @returns {string} Key associated with this controller's current payload snapshot. + */ + idempotencyKeyFor(payload) { + const serializedPayload = JSON.stringify(payload); + if (this.submissionPayloadSnapshot !== serializedPayload) { + this.submissionPayloadSnapshot = serializedPayload; + this.submissionIdempotencyKey = PopupsAPI.idempotencyKey(); + } + return this.submissionIdempotencyKey; + } + + /** + * Forget the retry identity after a parsed success or confirmed cancellation. + * Failures intentionally keep it, because the backend may already have accepted + * the request even though the visitor has not received its response. + * + * @returns {void} + */ + resetSubmissionRequest() { + this.submissionPayloadSnapshot = null; + this.submissionIdempotencyKey = null; + } + + /** + * Preserve checkbox choices as booleans and other values as entered strings. + * Reading checkbox.value would lose whether the visitor actually checked it. + * + * @param {PopupInput} input - Field to read without mutating its value. + * @returns {string | boolean} Submitted representation of the field's current value. + */ + inputValue(input) { + if (input.type === 'checkbox') return input.checked; + return input.value; + } + + /** + * Associate inputs through the server's step IDs rather than DOM nesting. + * Layout wrappers can change without changing validation or payload grouping. + * + * @param {HTMLElement} step - Step carrying a data-step-id attribute. + * @returns {PopupInput[]} Inputs whose data-popup-step-id matches this step. + */ + inputsForStep(step) { + return this.inputTargets.filter(input => input.dataset.popupStepId === step.dataset.stepId); + } + + /** + * Prioritize required email/phone fields for local completion identity selection. + * Preserve DOM order within the required and optional groups. + * + * @returns {PopupInput[]} Identity fields ordered by required status, then DOM order. + */ + get identityInputs() { + const inputs = this.inputTargets.filter(input => { + return ['email', 'phone'].includes(input.dataset.popupFieldKind); + }); + return inputs.filter(input => input.required).concat(inputs.filter(input => !input.required)); + } + + /** + * Snapshot completion text nodes before the first placeholder replacement. + * Reusing the original templates supports a corrected destination on a later + * submission while preserving surrounding markup and existing DOM references. + * + * @returns {Array<{node: Text, template: string}>} Cached nodes and their original text. + */ + get completionTextTemplates() { + if (this._completionTextTemplates) return this._completionTextTemplates; + const walker = document.createTreeWalker(this.completedTarget, NodeFilter.SHOW_TEXT); + this._completionTextTemplates = []; + while (walker.nextNode()) { + this._completionTextTemplates.push({ + node: walker.currentNode, + template: walker.currentNode.nodeValue + }); + } + return this._completionTextTemplates; + } + + /** + * Evaluate the dashboard's device target against the current viewport. + * The 768px split matches the API's automatic device selection; all or unspecified + * targets are unrestricted. This check runs when called, not on a resize listener. + * + * @returns {boolean} Whether this viewport is eligible to display the popup. + */ + matchesDevice() { + if (this.deviceValue === 'all') return true; + if (this.deviceValue === 'mobile') return window.innerWidth < 768; + if (this.deviceValue === 'desktop') return window.innerWidth >= 768; + return true; + } + + /** + * Resolve the active step from the server-rendered sequence and local progress. + * + * @returns {HTMLElement | undefined} Step at the current index, if present. + */ + get currentStep() { + return this.stepTargets[this.stepIndex]; + } + + /** + * Select the active step's fields for progression validation and inline errors. + * Requires a current step; the server renders this controller only for a flow + * with steps, and navigation selects indices from that rendered sequence. + * + * @returns {PopupInput[]} Fields associated with the current step. + */ + get currentStepInputs() { + return this.inputsForStep(this.currentStep); + } +} \ No newline at end of file diff --git a/lib/core/configuration.cjs b/lib/core/configuration.cjs index eb52c67..63af9ba 100644 --- a/lib/core/configuration.cjs +++ b/lib/core/configuration.cjs @@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { exports.Configuration = void 0; var _forms = require("./configuration/forms"); var _locale = require("./configuration/locale"); +var _popup = require("./configuration/popup"); var _webchat = require("./configuration/webchat"); var _whatsapp = require("./configuration/whatsapp"); var _push = require("./configuration/push"); @@ -16,6 +17,7 @@ var _push = require("./configuration/push"); * @property {Boolean} [autoGenerateSession=true] - whether to auto generate session or not * @property {String} [session] - session id * @property {Forms} [forms] - form configuration + * @property {Popup} [popup] - popup configuration * @property {Webchat} [webchat] - webchat configuration * @property {WhatsApp} [whatsappWidget] - WhatsApp widget configuration * @property {Push} [push] - push subscription configuration @@ -27,6 +29,7 @@ class Configuration { static autoGenerateSession = true; static session = null; static forms = _forms.Forms; + static popup = _popup.Popup; static webchat = _webchat.Webchat; static whatsapp = _whatsapp.WhatsApp; static push = _push.Push; @@ -45,6 +48,8 @@ class Configuration { Object.entries(props).forEach(([key, value]) => { if (key === 'forms') { this.forms = _forms.Forms.assign(value); + } else if (key === 'popup') { + this.popup = _popup.Popup.assign(value); } else if (key === 'webchat') { this.webchat = _webchat.Webchat.assign(value); } else if (key === 'whatsappWidget') { diff --git a/lib/core/configuration.js b/lib/core/configuration.js index 37321e9..4a4d04f 100644 --- a/lib/core/configuration.js +++ b/lib/core/configuration.js @@ -1,5 +1,6 @@ import { Forms } from './configuration/forms'; import { Locale } from './configuration/locale'; +import { Popup } from './configuration/popup'; import { Webchat } from './configuration/webchat'; import { WhatsApp } from './configuration/whatsapp'; import { Push } from './configuration/push'; @@ -11,6 +12,7 @@ import { Push } from './configuration/push'; * @property {Boolean} [autoGenerateSession=true] - whether to auto generate session or not * @property {String} [session] - session id * @property {Forms} [forms] - form configuration + * @property {Popup} [popup] - popup configuration * @property {Webchat} [webchat] - webchat configuration * @property {WhatsApp} [whatsappWidget] - WhatsApp widget configuration * @property {Push} [push] - push subscription configuration @@ -22,6 +24,7 @@ class Configuration { static autoGenerateSession = true; static session = null; static forms = Forms; + static popup = Popup; static webchat = Webchat; static whatsapp = WhatsApp; static push = Push; @@ -40,6 +43,8 @@ class Configuration { Object.entries(props).forEach(([key, value]) => { if (key === 'forms') { this.forms = Forms.assign(value); + } else if (key === 'popup') { + this.popup = Popup.assign(value); } else if (key === 'webchat') { this.webchat = Webchat.assign(value); } else if (key === 'whatsappWidget') { diff --git a/lib/core/configuration/popup.cjs b/lib/core/configuration/popup.cjs new file mode 100644 index 0000000..ec3f90d --- /dev/null +++ b/lib/core/configuration/popup.cjs @@ -0,0 +1,53 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.Popup = void 0; +/** + * @typedef {'auto' | 'mobile' | 'desktop'} PopupDevice + * @description Runtime device override for popup loading. + */ + +/** + * @class Popup + * @classdesc Configuration for dashboard popups. + * @property {String} id - The popup id. + * @property {String} container - The container to append the popup to, defaults to 'body'. + * @property {PopupDevice} device - Runtime device preference, defaults to 'auto'. + */ +class Popup { + static _id; + static _container = 'body'; + static _device = 'auto'; + static set id(value) { + this._id = value; + } + static get id() { + return this._id; + } + static set container(value) { + this._container = value; + } + static get container() { + return this._container; + } + static set device(value) { + if (!['auto', 'mobile', 'desktop'].includes(value)) { + throw new Error(`Invalid popup device value: ${value}`); + } + this._device = value; + } + static get device() { + return this._device; + } + static assign(props) { + if (props) { + Object.entries(props).forEach(([key, value]) => { + this[key] = value; + }); + } + return this; + } +} +exports.Popup = Popup; \ No newline at end of file diff --git a/lib/core/configuration/popup.js b/lib/core/configuration/popup.js new file mode 100644 index 0000000..3492da2 --- /dev/null +++ b/lib/core/configuration/popup.js @@ -0,0 +1,47 @@ +/** + * @typedef {'auto' | 'mobile' | 'desktop'} PopupDevice + * @description Runtime device override for popup loading. + */ + +/** + * @class Popup + * @classdesc Configuration for dashboard popups. + * @property {String} id - The popup id. + * @property {String} container - The container to append the popup to, defaults to 'body'. + * @property {PopupDevice} device - Runtime device preference, defaults to 'auto'. + */ +class Popup { + static _id; + static _container = 'body'; + static _device = 'auto'; + static set id(value) { + this._id = value; + } + static get id() { + return this._id; + } + static set container(value) { + this._container = value; + } + static get container() { + return this._container; + } + static set device(value) { + if (!['auto', 'mobile', 'desktop'].includes(value)) { + throw new Error(`Invalid popup device value: ${value}`); + } + this._device = value; + } + static get device() { + return this._device; + } + static assign(props) { + if (props) { + Object.entries(props).forEach(([key, value]) => { + this[key] = value; + }); + } + return this; + } +} +export { Popup }; \ No newline at end of file diff --git a/lib/core/event.cjs b/lib/core/event.cjs index 6e13027..7df7edd 100644 --- a/lib/core/event.cjs +++ b/lib/core/event.cjs @@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { exports.default = void 0; var _errors = require("../errors"); class Event { - static events = ['session-set', 'utm-set', 'forms:collected', 'form:completed', 'alert:shown', 'alert:dismissed', 'alert:accepted', 'webchat:mounted', 'webchat:opened', 'webchat:closed', 'webchat:message:sent', 'webchat:message:received', 'cart.added']; + static events = ['session-set', 'utm-set', 'forms:collected', 'form:completed', 'popup:mounted', 'popup:opened', 'popup:closed', 'alert:shown', 'alert:dismissed', 'alert:accepted', 'webchat:mounted', 'webchat:opened', 'webchat:closed', 'webchat:message:sent', 'webchat:message:received', 'cart.added']; static valid(name) { return Event.exists(name); } diff --git a/lib/core/event.js b/lib/core/event.js index 621dc5a..0a0a838 100644 --- a/lib/core/event.js +++ b/lib/core/event.js @@ -1,6 +1,6 @@ import { InvalidEvent } from '../errors'; export default class Event { - static events = ['session-set', 'utm-set', 'forms:collected', 'form:completed', 'alert:shown', 'alert:dismissed', 'alert:accepted', 'webchat:mounted', 'webchat:opened', 'webchat:closed', 'webchat:message:sent', 'webchat:message:received', 'cart.added']; + static events = ['session-set', 'utm-set', 'forms:collected', 'form:completed', 'popup:mounted', 'popup:opened', 'popup:closed', 'alert:shown', 'alert:dismissed', 'alert:accepted', 'webchat:mounted', 'webchat:opened', 'webchat:closed', 'webchat:message:sent', 'webchat:message:received', 'cart.added']; static valid(name) { return Event.exists(name); } diff --git a/lib/hellotext.cjs b/lib/hellotext.cjs index c53f0d2..24299b1 100644 --- a/lib/hellotext.cjs +++ b/lib/hellotext.cjs @@ -14,10 +14,12 @@ class Hellotext { static eventEmitter = new _core.Event(); static forms; static business; + static popup; static webchat; static whatsapp; static push; static alert; + static initializationVersion = 0; /** * initialize the module. @@ -25,6 +27,9 @@ class Hellotext { * @param { Configuration } config */ static async initialize(business, config = {}) { + const initializationVersion = ++this.initializationVersion; + this.popup?.unmount?.(); + this.popup = undefined; this.alert?.dispose(); this.alert = null; this.push?.dispose(); @@ -50,18 +55,44 @@ class Hellotext { this.alert = new _models.Alert(businessData.alert, businessContext, this.push); } } + const popupConfig = config.popup === false ? false : this.deepMergePlainObjects(businessData && businessData.popup || {}, config.popup || {}); const webchatConfig = config.webchat === false ? false : this.mergeWebchatConfig(businessData && businessData.webchat || {}, config.webchat || {}); const whatsappConfig = config.whatsappWidget === false ? false : this.mergeWhatsAppConfig(businessData && businessData.whatsapp || {}, config.whatsappWidget || {}); const hasExplicitBehaviourOverride = config.webchat && config.webchat !== false && Object.prototype.hasOwnProperty.call(config.webchat, 'behaviour'); _core.Configuration.webchat.behaviourOverride = hasExplicitBehaviourOverride; + const widgetLoads = []; if (webchatConfig && webchatConfig.id) { _core.Configuration.webchat.assign(webchatConfig); - this.webchat = await _models.Webchat.load(webchatConfig.id); + widgetLoads.push(_models.Webchat.load(webchatConfig.id).then(webchat => { + if (this.business === businessContext) this.webchat = webchat; + })); } if (whatsappConfig && whatsappConfig.id) { _core.Configuration.whatsapp.assign(whatsappConfig); - this.whatsapp = await _models.WhatsAppWidget.load(whatsappConfig.id); + widgetLoads.push(_models.WhatsAppWidget.load(whatsappConfig.id).then(whatsapp => { + if (this.business === businessContext) this.whatsapp = whatsapp; + })); } + if (popupConfig && popupConfig.id) { + const resolvedPopupConfig = { + container: 'body', + device: 'auto', + ...popupConfig + }; + _core.Configuration.popup.assign(resolvedPopupConfig); + widgetLoads.push(_models.Popup.load(resolvedPopupConfig.id, { + container: resolvedPopupConfig.container, + shouldMount: () => { + return this.business === businessContext && this.initializationVersion === initializationVersion; + } + }).then(popup => { + if (this.business === businessContext && this.initializationVersion === initializationVersion) { + this.popup = popup; + } + })); + } + await Promise.all(widgetLoads); + if (this.business !== businessContext || this.initializationVersion !== initializationVersion) return; if (typeof MutationObserver !== 'undefined') { this.forms.collectExistingFormsOnPage(); } diff --git a/lib/hellotext.js b/lib/hellotext.js index d5718b7..39c9967 100644 --- a/lib/hellotext.js +++ b/lib/hellotext.js @@ -1,15 +1,17 @@ import { Configuration, Event } from './core'; import API, { Response, keepaliveFor } from './api'; -import { Alert, Business, Fingerprint, FormCollection, Page, Push, Query, Session, User, Webchat, WhatsAppWidget } from './models'; +import { Alert, Business, Fingerprint, FormCollection, Page, Popup, Push, Query, Session, User, Webchat, WhatsAppWidget } from './models'; import { NotInitializedError } from './errors'; class Hellotext { static eventEmitter = new Event(); static forms; static business; + static popup; static webchat; static whatsapp; static push; static alert; + static initializationVersion = 0; /** * initialize the module. @@ -17,6 +19,9 @@ class Hellotext { * @param { Configuration } config */ static async initialize(business, config = {}) { + const initializationVersion = ++this.initializationVersion; + this.popup?.unmount?.(); + this.popup = undefined; this.alert?.dispose(); this.alert = null; this.push?.dispose(); @@ -42,18 +47,44 @@ class Hellotext { this.alert = new Alert(businessData.alert, businessContext, this.push); } } + const popupConfig = config.popup === false ? false : this.deepMergePlainObjects(businessData && businessData.popup || {}, config.popup || {}); const webchatConfig = config.webchat === false ? false : this.mergeWebchatConfig(businessData && businessData.webchat || {}, config.webchat || {}); const whatsappConfig = config.whatsappWidget === false ? false : this.mergeWhatsAppConfig(businessData && businessData.whatsapp || {}, config.whatsappWidget || {}); const hasExplicitBehaviourOverride = config.webchat && config.webchat !== false && Object.prototype.hasOwnProperty.call(config.webchat, 'behaviour'); Configuration.webchat.behaviourOverride = hasExplicitBehaviourOverride; + const widgetLoads = []; if (webchatConfig && webchatConfig.id) { Configuration.webchat.assign(webchatConfig); - this.webchat = await Webchat.load(webchatConfig.id); + widgetLoads.push(Webchat.load(webchatConfig.id).then(webchat => { + if (this.business === businessContext) this.webchat = webchat; + })); } if (whatsappConfig && whatsappConfig.id) { Configuration.whatsapp.assign(whatsappConfig); - this.whatsapp = await WhatsAppWidget.load(whatsappConfig.id); + widgetLoads.push(WhatsAppWidget.load(whatsappConfig.id).then(whatsapp => { + if (this.business === businessContext) this.whatsapp = whatsapp; + })); } + if (popupConfig && popupConfig.id) { + const resolvedPopupConfig = { + container: 'body', + device: 'auto', + ...popupConfig + }; + Configuration.popup.assign(resolvedPopupConfig); + widgetLoads.push(Popup.load(resolvedPopupConfig.id, { + container: resolvedPopupConfig.container, + shouldMount: () => { + return this.business === businessContext && this.initializationVersion === initializationVersion; + } + }).then(popup => { + if (this.business === businessContext && this.initializationVersion === initializationVersion) { + this.popup = popup; + } + })); + } + await Promise.all(widgetLoads); + if (this.business !== businessContext || this.initializationVersion !== initializationVersion) return; if (typeof MutationObserver !== 'undefined') { this.forms.collectExistingFormsOnPage(); } diff --git a/lib/index.cjs b/lib/index.cjs index 5590c6f..fc918ae 100644 --- a/lib/index.cjs +++ b/lib/index.cjs @@ -9,12 +9,14 @@ var _hellotext = _interopRequireDefault(require("./hellotext")); var _alert_controller = _interopRequireDefault(require("./controllers/alert_controller")); var _form_controller = _interopRequireDefault(require("./controllers/form_controller")); var _message_controller = _interopRequireDefault(require("./controllers/message_controller")); +var _popup_controller = _interopRequireDefault(require("./controllers/popup_controller")); var _emoji_picker_controller = _interopRequireDefault(require("./controllers/webchat/emoji_picker_controller")); var _webchat_controller = _interopRequireDefault(require("./controllers/webchat_controller")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const application = _stimulus.Application.start(); application.register('hellotext--alert', _alert_controller.default); application.register('hellotext--form', _form_controller.default); +application.register('hellotext--popup', _popup_controller.default); application.register('hellotext--webchat', _webchat_controller.default); application.register('hellotext--webchat--emoji', _emoji_picker_controller.default); application.register('hellotext--message', _message_controller.default); diff --git a/lib/index.js b/lib/index.js index 4fdd4a3..5ab3c77 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,11 +3,13 @@ import Hellotext from './hellotext'; import AlertController from './controllers/alert_controller'; import FormController from './controllers/form_controller'; import MessageController from './controllers/message_controller'; +import PopupController from './controllers/popup_controller'; import WebChatEmojiController from './controllers/webchat/emoji_picker_controller'; import WebchatController from './controllers/webchat_controller'; const application = Application.start(); application.register('hellotext--alert', AlertController); application.register('hellotext--form', FormController); +application.register('hellotext--popup', PopupController); application.register('hellotext--webchat', WebchatController); application.register('hellotext--webchat--emoji', WebChatEmojiController); application.register('hellotext--message', MessageController); diff --git a/lib/models/business.cjs b/lib/models/business.cjs index da65104..21f0e93 100644 --- a/lib/models/business.cjs +++ b/lib/models/business.cjs @@ -32,7 +32,9 @@ const stylesheetLoadTimeout = 10000; * @property {Object} [features] - Feature flags enabled for the business. * @property {String} [locale] - Default dashboard locale for the business. * @property {String} [style_url] - Stylesheet URL to inject for dashboard-managed surfaces. + * @property {{id: String}|null} [popup] - Dashboard popup defaults. * @property {BusinessWebchat|null} [webchat] - Dashboard webchat defaults. + * @property {{id: String}|null} [whatsapp] - Dashboard WhatsApp widget defaults. * @property {{public_key: String}|null} [push] - Push public key. * @property {{html: String}|null} [alert] - Smart Alert HTML. * @property {String|Array} [whitelist] - Domain whitelist configuration. diff --git a/lib/models/business.js b/lib/models/business.js index 4c0de5b..42b524b 100644 --- a/lib/models/business.js +++ b/lib/models/business.js @@ -25,7 +25,9 @@ const stylesheetLoadTimeout = 10000; * @property {Object} [features] - Feature flags enabled for the business. * @property {String} [locale] - Default dashboard locale for the business. * @property {String} [style_url] - Stylesheet URL to inject for dashboard-managed surfaces. + * @property {{id: String}|null} [popup] - Dashboard popup defaults. * @property {BusinessWebchat|null} [webchat] - Dashboard webchat defaults. + * @property {{id: String}|null} [whatsapp] - Dashboard WhatsApp widget defaults. * @property {{public_key: String}|null} [push] - Push public key. * @property {{html: String}|null} [alert] - Smart Alert HTML. * @property {String|Array} [whitelist] - Domain whitelist configuration. diff --git a/lib/models/index.cjs b/lib/models/index.cjs index e3ee34c..ed0ba45 100644 --- a/lib/models/index.cjs +++ b/lib/models/index.cjs @@ -45,6 +45,12 @@ Object.defineProperty(exports, "Page", { return _page.Page; } }); +Object.defineProperty(exports, "Popup", { + enumerable: true, + get: function () { + return _popup.Popup; + } +}); Object.defineProperty(exports, "Push", { enumerable: true, get: function () { @@ -94,6 +100,7 @@ var _fingerprint = require("./fingerprint"); var _form = require("./form"); var _form_collection = require("./form_collection"); var _page = require("./page"); +var _popup = require("./popup"); var _push = require("./push"); var _query = require("./query"); var _session = require("./session"); diff --git a/lib/models/index.js b/lib/models/index.js index d1c6493..ec384ed 100644 --- a/lib/models/index.js +++ b/lib/models/index.js @@ -5,6 +5,7 @@ export { Fingerprint } from './fingerprint'; export { Form } from './form'; export { FormCollection } from './form_collection'; export { Page } from './page'; +export { Popup } from './popup'; export { Push } from './push'; export { Query } from './query'; export { Session } from './session'; diff --git a/lib/models/popup.cjs b/lib/models/popup.cjs new file mode 100644 index 0000000..7b77259 --- /dev/null +++ b/lib/models/popup.cjs @@ -0,0 +1,61 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.Popup = void 0; +var _core = require("../core"); +var _api = _interopRequireDefault(require("../api")); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +class Popup { + static async load(id, options = {}) { + const popup = new Popup({ + id, + html: await _api.default.popups.get(id) + }, options); + popup.rendered = popup.render(); + return popup; + } + constructor(data, { + container = _core.Configuration.popup.container, + shouldMount = () => true + } = {}) { + this.data = data; + this.container = container; + this.mounted = false; + this.rendered = Promise.resolve(false); + this.shouldMount = shouldMount; + } + async render() { + if (!this.data.html || !this.shouldMount()) return false; + const container = this.containerToAppendTo; + if (!container) { + console.warn(`Hellotext popup was not mounted because the container ${this.container} was not found.`); + return false; + } + if (!this.shouldMount()) return false; + container.appendChild(this.data.html); + this.mounted = true; + if (!this.shouldMount()) this.unmount(); + return this.mounted; + } + + /** + * Remove this popup's server-rendered surface when a later initialization + * replaces or disables it. Removing the root also disconnects Stimulus. + * + * @returns {void} + */ + unmount() { + this.data.html?.remove(); + this.mounted = false; + } + get containerToAppendTo() { + try { + return document.querySelector(this.container); + } catch (_) { + return null; + } + } +} +exports.Popup = Popup; \ No newline at end of file diff --git a/lib/models/popup.js b/lib/models/popup.js new file mode 100644 index 0000000..36ceb24 --- /dev/null +++ b/lib/models/popup.js @@ -0,0 +1,54 @@ +import { Configuration } from '../core'; +import API from '../api'; +class Popup { + static async load(id, options = {}) { + const popup = new Popup({ + id, + html: await API.popups.get(id) + }, options); + popup.rendered = popup.render(); + return popup; + } + constructor(data, { + container = Configuration.popup.container, + shouldMount = () => true + } = {}) { + this.data = data; + this.container = container; + this.mounted = false; + this.rendered = Promise.resolve(false); + this.shouldMount = shouldMount; + } + async render() { + if (!this.data.html || !this.shouldMount()) return false; + const container = this.containerToAppendTo; + if (!container) { + console.warn(`Hellotext popup was not mounted because the container ${this.container} was not found.`); + return false; + } + if (!this.shouldMount()) return false; + container.appendChild(this.data.html); + this.mounted = true; + if (!this.shouldMount()) this.unmount(); + return this.mounted; + } + + /** + * Remove this popup's server-rendered surface when a later initialization + * replaces or disables it. Removing the root also disconnects Stimulus. + * + * @returns {void} + */ + unmount() { + this.data.html?.remove(); + this.mounted = false; + } + get containerToAppendTo() { + try { + return document.querySelector(this.container); + } catch (_) { + return null; + } + } +} +export { Popup }; \ No newline at end of file diff --git a/package.json b/package.json index 3356c52..c17dfee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hellotext/hellotext", - "version": "2.5.9", + "version": "2.5.10", "description": "Hellotext JavaScript Client", "source": "src/index.js", "main": "lib/index.cjs", diff --git a/src/api/index.js b/src/api/index.js index d60fe29..61f39f5 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -2,6 +2,7 @@ import BusinessesAPI from './businesses' import EventsAPI from './events' import FormsAPI from './forms' import IdentificationsAPI from './identifications' +import PopupsAPI from './popups' import WebchatsAPI from './webchats' import WhatsAppWidgetsAPI from './whatsapp_widgets' import AcksAPI from './acks' @@ -44,6 +45,14 @@ export default class API { return FormsAPI } + static get popups() { + return PopupsAPI + } + + static get popups() { + return PopupsAPI + } + static get webchats() { return WebchatsAPI } diff --git a/src/api/popups.js b/src/api/popups.js new file mode 100644 index 0000000..f99c105 --- /dev/null +++ b/src/api/popups.js @@ -0,0 +1,104 @@ +import { Configuration, Locale } from '../core' +import Hellotext from '../hellotext' + +import { Response } from './response' + +class PopupsAPI { + static get endpoint() { + return Configuration.endpoint('public/popups') + } + + static async get(id) { + const url = new URL(`${this.endpoint}/${id}`) + + url.searchParams.append('session', Hellotext.session) + url.searchParams.append('locale', Locale.toString()) + url.searchParams.append('device', this.runtimeDevice) + + const response = await this.fetchPopup(url) + + if (!response.ok) return null + + const data = await this.parsePopupResponse(response) + + if (!data) return null + + if (!Hellotext.business.data) { + Hellotext.business.setData(data.business) + Hellotext.business.setLocale(data.locale) + } + + return new DOMParser().parseFromString(data.html, 'text/html').querySelector('article') + } + + static async submit(id, data, idempotencyKey = this.idempotencyKey()) { + const response = await fetch(`${this.endpoint}/${id}/submissions`, { + method: 'POST', + headers: { + ...Hellotext.headers, + 'Idempotency-Key': idempotencyKey, + }, + body: JSON.stringify({ + session: Hellotext.session, + popup_submission: data, + }), + }) + + return new Response(response.ok, response) + } + + static async resend(id, submissionId, token) { + const response = await fetch(`${this.endpoint}/${id}/submissions/${submissionId}/resend`, { + method: 'POST', + headers: Hellotext.headers, + body: JSON.stringify({ token }), + }) + + return new Response(response.ok, response) + } + + static async cancel(id, submissionId, token) { + const response = await fetch(`${this.endpoint}/${id}/submissions/${submissionId}/cancel`, { + method: 'POST', + headers: Hellotext.headers, + body: JSON.stringify({ token }), + }) + + return new Response(response.ok, response) + } + + static idempotencyKey() { + if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { + return crypto.randomUUID() + } + + return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}` + } + + static async fetchPopup(url) { + try { + return await fetch(url, { + method: 'GET', + headers: Hellotext.headers, + }) + } catch (_) { + return { ok: false } + } + } + + static get runtimeDevice() { + if (Configuration.popup.device !== 'auto') return Configuration.popup.device + + return window.innerWidth <= 767 ? 'mobile' : 'desktop' + } + + static async parsePopupResponse(response) { + try { + return await response.json() + } catch (_) { + return null + } + } +} + +export default PopupsAPI diff --git a/src/controllers/popup_controller.js b/src/controllers/popup_controller.js new file mode 100644 index 0000000..717b1b0 --- /dev/null +++ b/src/controllers/popup_controller.js @@ -0,0 +1,919 @@ +import { Controller } from '@hotwired/stimulus' + +import PopupsAPI from '../api/popups' +import Hellotext from '../hellotext' + +/** + * An input rendered by the popup's server-side field components. + * + * @typedef {HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement} PopupInput + */ + +/** + * Identity used for completion copy and for locating the field to edit. + * A backend destination may have no matching input in the rendered form. + * + * @typedef {Object} PopupIdentity + * @property {PopupInput | undefined} input - Field associated with the destination. + * @property {'email' | 'phone'} kind - Field kind, distinct from the delivery channel. + * @property {string} value - Destination to display to the visitor. + */ + +/** + * Collected values retain both field lookup and their original step grouping. + * Checkbox values are booleans; other field values remain strings. + * + * @typedef {Object} PopupSubmissionPayload + * @property {string} [email] - Email input value for backend identity handling. + * @property {string} [phone] - Phone input value for backend identity handling. + * @property {Object} metadata + * @property {Object} metadata.capture - Capture metadata supplied by the server. + * @property {Object} metadata.fields - Values keyed by field identifier. + * @property {Array<{id: string, name: string, fields: Object}>} metadata.steps + */ + +/** + * A backend validation error, optionally associated with a built-in or custom field. + * + * @typedef {Object} PopupSubmissionError + * @property {string} [parameter] - Built-in field kind or custom property identifier. + * @property {string} [description] - Message suitable for displaying to the visitor. + */ + +/** + * Controls a dashboard popup rendered by Popup::RuntimeComponent on merchant sites. + * + * The server owns the markup, styling, and initial hidden attributes: the bubble, + * dialog, later steps, and completion state arrive hidden. This controller chooses + * when to reveal them and manages the visitor's progress through the existing DOM. + * State initialized here belongs to one controller instance, not persistent storage. + * + * A successful submission opens the completion screen even when verification is + * pending. The backend owns delivery routing and verification; this controller + * displays the returned state and requests resends or cancellation using its token. + * + * Targets: + * - bubble: Launcher shown before the popup when bubble mode is enabled. + * - dialog: Popup dialog/surface wrapper. + * - step: Sequential form steps. + * - completed: Completion state shown after submission. + * - input: User-entered popup fields. + * - submitButton: Step buttons disabled while the submission is in flight. + * - globalError: Submission errors that cannot be shown beside an input. + * - resendButton: Delivery resend action and its localized countdown label. + * - changeDestinationButton: Action that returns to the delivered-to identity field. + * - deliveryCopy: Completion headline and description shown when a delivery is queued. + * - noDeliveryCopy: Server-rendered completion copy shown when no delivery is required. + * + * Values: + * - capture: Capture metadata supplied by the server and included in submissions. + * - device: Popup device targeting. + * - hasBubble: Whether the popup starts from a bubble. + * - id: Public popup identifier. + */ +export default class extends Controller { + static targets = [ + 'bubble', + 'dialog', + 'step', + 'completed', + 'input', + 'submitButton', + 'globalError', + 'resendButton', + 'changeDestinationButton', + 'deliveryCopy', + 'noDeliveryCopy', + ] + + static values = { + capture: Object, + device: String, + hasBubble: Boolean, + id: String, + } + + /** + * Establish progress and preserve the original resend label once per instance. + * Keeping this outside connect() avoids resetting progress or capturing the + * temporary countdown text when Stimulus reconnects the same controller. + * + * @returns {void} + */ + initialize() { + this.stepIndex = 0 + this.resendLabel = this.hasResendButtonTarget ? this.resendButtonTarget.textContent.trim() : '' + } + + /** + * Announce that the popup has joined the DOM before applying the display policy. + * Mounting does not imply dialog visibility: the server supplies hidden markup, + * and device targeting or bubble mode may keep the dialog closed. + * + * @returns {void} + */ + connect() { + Hellotext.eventEmitter.dispatch('popup:mounted') + this.evaluateDisplay() + } + + /** + * Stop the countdown interval when detached so it does not keep updating old DOM. + * + * @returns {void} + */ + disconnect() { + this.stopResendCooldown() + } + + /** + * Replace the launcher with the dialog inside an already eligible popup. + * Subscribers are notified when the dialog is revealed, not when the bubble appears. + * + * @param {Event} [event] - Optional launcher interaction whose default action is prevented. + * @returns {void} + */ + open(event) { + if (event) event.preventDefault() + if (this.hasBubbleTarget) this.bubbleTarget.hidden = true + + this.dialogTarget.hidden = false + Hellotext.eventEmitter.dispatch('popup:opened') + } + + /** + * Dismiss the entire popup and remember that choice for this controller instance. + * Closing changes visibility; it does not cancel a submission or its delivery. + * + * @param {Event} [event] - Optional close-button interaction. + * @returns {void} + */ + close(event) { + if (event) event.preventDefault() + + this.dismissed = true + this.dialogTarget.hidden = true + + if (this.hasBubbleTarget) this.bubbleTarget.hidden = true + + this.element.hidden = true + Hellotext.eventEmitter.dispatch('popup:closed') + } + + /** + * Validate the current step before advancing, or submit if this is the final step. + * Clear previous server validity errors first so corrected values can be checked. + * + * @param {Event} [event] - Optional step-button interaction. + * @returns {Promise} + */ + async next(event) { + if (event) event.preventDefault() + + this.clearCustomValidity() + + if (!this.currentStepValid()) { + this.showErrorMessages(this.currentStepInputs) + return + } + + this.clearErrorMessages(this.currentStepInputs) + + if (this.stepIndex < this.stepTargets.length - 1) { + this.showStep(this.stepIndex + 1) + return + } + + await this.submit() + } + + /** + * Send the collected steps only after the final step passes validation. + * Earlier form submissions act as Next, preserving the same progression for Enter + * and button clicks. Failures leave the form available for a deliberate retry. + * + * @param {Event} [event] - Optional form submission or final-button interaction. + * @returns {Promise} + */ + async submit(event) { + if (event) event.preventDefault() + + this.clearCustomValidity() + + if (this.stepIndex < this.stepTargets.length - 1) { + await this.next() + return + } + + if (!this.currentStepValid()) { + this.showErrorMessages(this.currentStepInputs) + return + } + + this.clearErrorMessages(this.currentStepInputs) + this.clearGlobalError() + + this.submitButtonTargets.forEach(button => { + button.disabled = true + }) + + try { + const payload = this.submissionPayload() + const response = await PopupsAPI.submit( + this.idValue, + payload, + this.idempotencyKeyFor(payload), + ) + + if (response.failed) { + await this.handleSubmissionError(response) + return + } + + // Keep the backend's chosen route and action token together. Resend and edit + // must act on this accepted submission, even if a fallback route was selected. + const submission = await response.json() + this.submissionId = submission.id + this.submissionVerificationState = submission.verification_state + this.submissionActionToken = submission.action_token + this.submissionDeliveryStatus = submission.delivery_status + this.submissionDeliveryChannel = submission.delivery_channel + this.submissionDestination = submission.destination + this.resetSubmissionRequest() + } catch (_) { + // The server may have accepted a request whose response was lost. Retain the + // payload's idempotency key so another attempt can recover that submission. + this.showGlobalError() + return + } finally { + this.submitButtonTargets.forEach(button => { + button.disabled = false + }) + } + + this.showCompleted() + } + + /** + * Apply dismissal and viewport eligibility before revealing any popup surface. + * Hide the root on rejection so this also works after a previously visible mount. + * + * @returns {void} + */ + evaluateDisplay() { + if (this.dismissed || !this.matchesDevice()) { + this.element.hidden = true + return + } + + this.showInitialState() + } + + /** + * Choose the launcher or immediate dialog without resetting entered form values. + * Set both surface states explicitly because a reconnect can reuse modified DOM. + * Bubble display alone does not emit the dialog's popup:opened event. + * + * @returns {void} + */ + showInitialState() { + this.element.hidden = false + + if (this.hasBubbleValue && this.hasBubbleTarget) { + this.bubbleTarget.hidden = false + this.dialogTarget.hidden = true + return + } + + if (this.hasBubbleTarget) this.bubbleTarget.hidden = true + + this.dialogTarget.hidden = false + Hellotext.eventEmitter.dispatch('popup:opened') + } + + /** + * Reveal one existing step and leave completion, preserving all collected values. + * Also used after confirmed cancellation to return to the destination's own step. + * + * @param {number} index - Zero-based index of a step in the rendered flow. + * @returns {void} + */ + showStep(index) { + this.stepIndex = index + + this.stepTargets.forEach((step, stepIndex) => { + step.hidden = stepIndex !== index + }) + + this.completedTarget.hidden = true + } + + /** + * Replace the form steps with the result of an accepted submission. + * Completion reflects the response received so far; it does not assert that + * delivery or verification has finished, and it does not poll for later changes. + * + * @returns {void} + */ + showCompleted() { + this.stepTargets.forEach(step => { + step.hidden = true + }) + + this.interpolateCompletionCopy() + this.configureCompletionActions() + + this.completedTarget.hidden = false + } + + /** + * Fill destination/channel placeholders while preserving the server's rich markup. + * Replace text nodes from saved templates so visitor values stay text and a later + * corrected destination can replace the original placeholders again. + * + * @returns {void} + */ + interpolateCompletionCopy() { + const identity = this.completedIdentity + + if (!identity) return + + const replacements = { + destination: identity.value, + channel: this.submissionDeliveryChannel || identity.kind, + } + + this.completionTextTemplates.forEach(({ node, template }) => { + node.nodeValue = template.replace( + /\{(destination|channel)\}/g, + (placeholder, key) => replacements[key] || placeholder, + ) + }) + } + + /** + * Format a local identity for completion copy when backend route data is absent. + * Phone prefixes and leading-zero removal apply only to this display fallback; + * submissionPayload() still sends the original field value. + * + * @param {PopupInput} input - Email or phone field containing a string value. + * @returns {string} Trimmed identity with the configured phone prefix when needed. + */ + identityValue(input) { + const value = this.inputValue(input).trim() + if (input.dataset.popupFieldKind !== 'phone' || value.startsWith('+')) return value + + const prefix = input.dataset.popupPhonePrefix + + return prefix ? `${prefix}${value.replace(/^0+/, '')}` : value + } + + /** + * Configure follow-up actions from the backend's delivery and verification state. + * Contact-only submissions show saved-details copy. Queued, unverified deliveries + * with an action token expose resend after the initial one-minute cooldown. + * + * @returns {void} + */ + configureCompletionActions() { + const deliveryRequired = this.submissionDeliveryStatus !== 'not_required' + this.revealCompletionCopy(deliveryRequired) + + if (!deliveryRequired) { + this.completedTarget.querySelector('[data-delivery-actions]')?.setAttribute('hidden', '') + return + } + + const identity = this.completedIdentity + if (!identity) return + + if (this.hasChangeDestinationButtonTarget) { + this.changeDestinationButtonTarget.textContent = + this.changeDestinationButtonTarget.dataset[`${identity.kind}Label`] + this.changeDestinationButtonTarget.hidden = false + } + + if ( + this.submissionId && + this.submissionActionToken && + this.submissionDeliveryStatus === 'queued' && + this.submissionVerificationState === 'unverified' && + this.hasResendButtonTarget + ) { + this.resendButtonTarget.hidden = false + this.startResendCooldown(60) + } + } + + /** + * Request another delivery for the accepted submission using its action token. + * No edited destination is sent: the backend retains ownership of the route. + * Ignore repeated clicks while pending or cooling down; honor Retry-After on + * success or rate limiting, and allow a manual retry after other failures. + * + * @param {Event} [event] - Optional resend-button interaction. + * @returns {Promise} + */ + async resend(event) { + if (event) event.preventDefault() + if ( + !this.submissionId || + !this.submissionActionToken || + this.resendPending || + this.resendCooldownActive + ) + return + + const identity = this.completedIdentity + if (!identity) return + + this.resendPending = true + this.resendButtonTarget.disabled = true + + try { + const response = await PopupsAPI.resend( + this.idValue, + this.submissionId, + this.submissionActionToken, + ) + const retryAfter = Number(response.data.headers?.get('Retry-After')) || 60 + + if (response.succeeded || response.data.status === 429) { + this.startResendCooldown(retryAfter) + } else { + this.resendButtonTarget.disabled = false + } + } catch (_) { + this.resendButtonTarget.disabled = false + } finally { + this.resendPending = false + } + } + + /** + * Cancel the accepted submission before allowing its destination to be edited. + * Returning to the form before confirmation could create a replacement while the + * previous submission remains deliverable. On failure, keep its state and the + * completion screen; on success, focus the field matching the backend's route. + * + * @param {Event} [event] - Optional change-email or change-phone interaction. + * @returns {Promise} + */ + async changeDestination(event) { + if (event) event.preventDefault() + if (!this.submissionId || !this.submissionActionToken || this.changeDestinationPending) return + + const input = this.completedIdentity?.input + if (!input) return + + const stepIndex = this.stepTargets.findIndex( + step => step.dataset.stepId === input.dataset.popupStepId, + ) + if (stepIndex < 0) return + + this.changeDestinationPending = true + this.changeDestinationButtonTarget.disabled = true + + try { + const response = await PopupsAPI.cancel( + this.idValue, + this.submissionId, + this.submissionActionToken, + ) + if (response.failed) return + + this.stopResendCooldown() + this.submissionId = null + this.submissionActionToken = null + this.submissionVerificationState = null + this.submissionDeliveryStatus = null + this.submissionDeliveryChannel = null + this.submissionDestination = null + this.resetSubmissionRequest() + this.showStep(stepIndex) + input.focus() + } catch (_) { + // Keep Completed visible when cancellation cannot be confirmed. Starting + // a replacement submission before that boundary could deliver twice. + } finally { + this.changeDestinationPending = false + this.changeDestinationButtonTarget.disabled = false + } + } + + /** + * Replace any countdown and immediately reflect its remaining time in the button. + * Store a deadline rather than decrementing a counter so delayed timer callbacks + * do not lengthen the cooldown when the browser throttles background tabs. + * + * @param {number} seconds - Cooldown duration, clamped to at least one second. + * @returns {void} + */ + startResendCooldown(seconds) { + this.stopResendCooldown() + this.resendCooldownEndsAt = Date.now() + Math.max(seconds, 1) * 1000 + this.updateResendCountdown() + this.resendTimer = window.setInterval(() => this.updateResendCountdown(), 1000) + } + + /** + * Clear the timer and deadline. Callers own the next button or screen state; + * stopping a timer during disconnect or cancellation must not reveal UI itself. + * + * @returns {void} + */ + stopResendCooldown() { + if (this.resendTimer) window.clearInterval(this.resendTimer) + this.resendTimer = null + this.resendCooldownEndsAt = null + } + + /** + * Render the localized remaining time, or restore the original label on expiry. + * Recompute from the deadline on each tick instead of assuming ticks are punctual. + * + * @returns {void} + */ + updateResendCountdown() { + const seconds = Math.max(0, Math.ceil((this.resendCooldownEndsAt - Date.now()) / 1000)) + + if (seconds === 0) { + this.stopResendCooldown() + this.resendButtonTarget.textContent = this.resendLabel + this.resendButtonTarget.disabled = false + return + } + + const time = `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}` + const template = this.resendButtonTarget.dataset.countdownLabel || `${this.resendLabel} %{time}` + this.resendButtonTarget.textContent = template.replace('%{time}', time) + this.resendButtonTarget.disabled = true + } + + /** + * Check the deadline independently of whether the latest timer tick has run. + * + * @returns {boolean} Whether a resend is still blocked by the local cooldown. + */ + get resendCooldownActive() { + return this.resendCooldownEndsAt > Date.now() + } + + /** + * Choose a populated local identity when no backend destination is available. + * Required fields take precedence; optional identities are a fallback. + * + * @returns {PopupIdentity | undefined} First populated identity in priority order. + */ + get completionIdentity() { + return this.identityInputs + .map(input => ({ + input, + kind: input.dataset.popupFieldKind, + value: this.identityValue(input), + })) + .find(({ value }) => value) + } + + /** + * Prefer the backend's actual destination so fallback delivery is represented + * accurately. Map non-email delivery channels, such as SMS or WhatsApp, back to + * the phone field for editing; retain the actual channel separately for copy. + * + * @returns {PopupIdentity | undefined} Backend identity or the local display fallback. + */ + get completedIdentity() { + if (this.submissionDestination && this.submissionDeliveryChannel) { + const kind = this.submissionDeliveryChannel === 'email' ? 'email' : 'phone' + const input = this.identityInputs.find(candidate => candidate.dataset.popupFieldKind === kind) + + return { input, kind, value: this.submissionDestination } + } + + return this.completionIdentity + } + + /** + * Reveal the completion copy that matches the delivery outcome. The server renders both + * variants and owns their markup; the controller only chooses which one is visible, so + * no completion structure is built here and interpolated text nodes are never replaced. + * + * @param {boolean} deliveryRequired - Whether the submission queued a delivery. + * @returns {void} + */ + revealCompletionCopy(deliveryRequired) { + if (this.hasDeliveryCopyTarget) { + this.deliveryCopyTargets.forEach(element => { + element.hidden = !deliveryRequired + }) + } + + if (this.hasNoDeliveryCopyTarget) { + this.noDeliveryCopyTargets.forEach(element => { + element.hidden = deliveryRequired + }) + } + } + + /** + * Apply the browser's constraints only to the step the visitor is completing. + * Required fields in later, hidden steps must not block earlier progression. + * + * @returns {boolean} Whether every input associated with the current step is valid. + */ + currentStepValid() { + return this.currentStepInputs.every(input => input.checkValidity()) + } + + /** + * Mirror native/custom validity messages into the server's inline error containers. + * Valid fields clear their old message; fields without a container are skipped. + * + * @param {PopupInput[]} inputs - Fields whose current validity should be displayed. + * @returns {void} + */ + showErrorMessages(inputs) { + inputs.forEach(input => { + const container = input + .closest('.hellotext--popup-field') + ?.querySelector('[data-error-container]') + if (!container) return + + container.textContent = input.validity.valid ? '' : input.validationMessage + }) + } + + /** + * Remove displayed field errors without changing values or validity constraints. + * + * @param {PopupInput[]} [inputs=this.inputTargets] - Fields to clear, defaulting to all. + * @returns {void} + */ + clearErrorMessages(inputs = this.inputTargets) { + inputs.forEach(input => { + const container = input + .closest('.hellotext--popup-field') + ?.querySelector('[data-error-container]') + if (container) container.textContent = '' + }) + } + + /** + * Remove server-set validity messages before validating a fresh attempt. + * Native constraints remain active; stale custom errors must not reject edits. + * + * @returns {void} + */ + clearCustomValidity() { + this.inputTargets.forEach(input => input.setCustomValidity('')) + } + + /** + * Clear and hide the optional form-level error before a new submission attempt. + * + * @returns {void} + */ + clearGlobalError() { + if (!this.hasGlobalErrorTarget) return + + this.globalErrorTarget.textContent = '' + this.globalErrorTarget.hidden = true + } + + /** + * Show a form-level failure with the server's localized fallback when needed. + * Render messages as text, and tolerate markup without a global-error target. + * + * @param {string | null} [message=null] - Specific error, or no value for fallback copy. + * @returns {void} + */ + showGlobalError(message = null) { + if (!this.hasGlobalErrorTarget) return + + this.globalErrorTarget.textContent = + message || this.globalErrorTarget.dataset.submitError || 'Unable to submit. Please try again.' + this.globalErrorTarget.hidden = false + } + + /** + * Route backend errors to matching fields or the form-level error container. + * Unreadable JSON or an empty errors list uses generic copy when the server + * cannot provide a structured validation explanation. + * + * @param {import('../api/response').Response} response - Failed submission response. + * @returns {Promise} + */ + async handleSubmissionError(response) { + let data + + try { + data = await response.json() + } catch (_) { + this.showGlobalError() + return + } + + const errors = data.errors || [] + const generalErrors = [] + + errors.forEach(error => { + const input = this.inputForError(error) + if (!input) { + if (error.description) generalErrors.push(error.description) + return + } + + input.setCustomValidity(error.description || input.validationMessage) + input.reportValidity() + }) + + this.showErrorMessages(this.inputTargets) + if (generalErrors.length) this.showGlobalError(generalErrors.join(' ')) + else if (!errors.length) this.showGlobalError() + } + + /** + * Match both built-in identity names and custom property keys in backend errors. + * Missing or unmatched parameters belong to the form-level error path. + * + * @param {PopupSubmissionError} error - Error identifying a field when possible. + * @returns {PopupInput | null | undefined} Matching input, or no match. + */ + inputForError(error) { + const parameter = error.parameter + if (!parameter) return null + + return this.inputTargets.find(input => { + return input.dataset.popupFieldKind === parameter || input.dataset.popupFieldKey === parameter + }) + } + + /** + * Collect the whole flow while preserving the dashboard's field and step identity. + * Top-level email/phone support backend identity handling; metadata retains all + * values, including custom properties and checkboxes, with their step context. + * + * @returns {PopupSubmissionPayload} Collected data before the API adds session context. + */ + submissionPayload() { + const payload = { + metadata: { + capture: this.captureValue || {}, + fields: {}, + steps: [], + }, + } + + this.stepTargets.forEach(step => { + const stepFields = {} + const inputs = this.inputsForStep(step) + + inputs.forEach(input => { + const value = this.inputValue(input) + const key = input.dataset.popupFieldKey || input.name + + stepFields[key] = value + payload.metadata.fields[key] = value + + if (input.dataset.popupFieldKind === 'email') payload.email = value + if (input.dataset.popupFieldKind === 'phone') payload.phone = value + }) + + payload.metadata.steps.push({ + id: step.dataset.stepId, + name: step.dataset.stepName, + fields: stepFields, + }) + }) + + return payload + } + + /** + * Reuse the request key while the serialized payload remains unchanged. + * A failed or unreadable response does not prove the submission was rejected; + * retaining the key lets a manual retry recover the same server-side operation. + * Changed values represent a new attempt and receive a fresh key. + * + * @param {PopupSubmissionPayload} payload - Data about to be submitted. + * @returns {string} Key associated with this controller's current payload snapshot. + */ + idempotencyKeyFor(payload) { + const serializedPayload = JSON.stringify(payload) + + if (this.submissionPayloadSnapshot !== serializedPayload) { + this.submissionPayloadSnapshot = serializedPayload + this.submissionIdempotencyKey = PopupsAPI.idempotencyKey() + } + + return this.submissionIdempotencyKey + } + + /** + * Forget the retry identity after a parsed success or confirmed cancellation. + * Failures intentionally keep it, because the backend may already have accepted + * the request even though the visitor has not received its response. + * + * @returns {void} + */ + resetSubmissionRequest() { + this.submissionPayloadSnapshot = null + this.submissionIdempotencyKey = null + } + + /** + * Preserve checkbox choices as booleans and other values as entered strings. + * Reading checkbox.value would lose whether the visitor actually checked it. + * + * @param {PopupInput} input - Field to read without mutating its value. + * @returns {string | boolean} Submitted representation of the field's current value. + */ + inputValue(input) { + if (input.type === 'checkbox') return input.checked + + return input.value + } + + /** + * Associate inputs through the server's step IDs rather than DOM nesting. + * Layout wrappers can change without changing validation or payload grouping. + * + * @param {HTMLElement} step - Step carrying a data-step-id attribute. + * @returns {PopupInput[]} Inputs whose data-popup-step-id matches this step. + */ + inputsForStep(step) { + return this.inputTargets.filter(input => input.dataset.popupStepId === step.dataset.stepId) + } + + /** + * Prioritize required email/phone fields for local completion identity selection. + * Preserve DOM order within the required and optional groups. + * + * @returns {PopupInput[]} Identity fields ordered by required status, then DOM order. + */ + get identityInputs() { + const inputs = this.inputTargets.filter(input => { + return ['email', 'phone'].includes(input.dataset.popupFieldKind) + }) + + return inputs.filter(input => input.required).concat(inputs.filter(input => !input.required)) + } + + /** + * Snapshot completion text nodes before the first placeholder replacement. + * Reusing the original templates supports a corrected destination on a later + * submission while preserving surrounding markup and existing DOM references. + * + * @returns {Array<{node: Text, template: string}>} Cached nodes and their original text. + */ + get completionTextTemplates() { + if (this._completionTextTemplates) return this._completionTextTemplates + + const walker = document.createTreeWalker(this.completedTarget, NodeFilter.SHOW_TEXT) + this._completionTextTemplates = [] + + while (walker.nextNode()) { + this._completionTextTemplates.push({ + node: walker.currentNode, + template: walker.currentNode.nodeValue, + }) + } + + return this._completionTextTemplates + } + + /** + * Evaluate the dashboard's device target against the current viewport. + * The 768px split matches the API's automatic device selection; all or unspecified + * targets are unrestricted. This check runs when called, not on a resize listener. + * + * @returns {boolean} Whether this viewport is eligible to display the popup. + */ + matchesDevice() { + if (this.deviceValue === 'all') return true + if (this.deviceValue === 'mobile') return window.innerWidth < 768 + if (this.deviceValue === 'desktop') return window.innerWidth >= 768 + + return true + } + + /** + * Resolve the active step from the server-rendered sequence and local progress. + * + * @returns {HTMLElement | undefined} Step at the current index, if present. + */ + get currentStep() { + return this.stepTargets[this.stepIndex] + } + + /** + * Select the active step's fields for progression validation and inline errors. + * Requires a current step; the server renders this controller only for a flow + * with steps, and navigation selects indices from that rendered sequence. + * + * @returns {PopupInput[]} Fields associated with the current step. + */ + get currentStepInputs() { + return this.inputsForStep(this.currentStep) + } +} diff --git a/src/core/configuration.js b/src/core/configuration.js index 9820469..6136ecb 100644 --- a/src/core/configuration.js +++ b/src/core/configuration.js @@ -1,5 +1,6 @@ import { Forms } from './configuration/forms' import { Locale } from './configuration/locale' +import { Popup } from './configuration/popup' import { Webchat } from './configuration/webchat' import { WhatsApp } from './configuration/whatsapp' import { Push } from './configuration/push' @@ -11,6 +12,7 @@ import { Push } from './configuration/push' * @property {Boolean} [autoGenerateSession=true] - whether to auto generate session or not * @property {String} [session] - session id * @property {Forms} [forms] - form configuration + * @property {Popup} [popup] - popup configuration * @property {Webchat} [webchat] - webchat configuration * @property {WhatsApp} [whatsappWidget] - WhatsApp widget configuration * @property {Push} [push] - push subscription configuration @@ -24,6 +26,7 @@ class Configuration { static session = null static forms = Forms + static popup = Popup static webchat = Webchat static whatsapp = WhatsApp static push = Push @@ -46,6 +49,8 @@ class Configuration { Object.entries(props).forEach(([key, value]) => { if (key === 'forms') { this.forms = Forms.assign(value) + } else if (key === 'popup') { + this.popup = Popup.assign(value) } else if (key === 'webchat') { this.webchat = Webchat.assign(value) } else if (key === 'whatsappWidget') { diff --git a/src/core/configuration/popup.js b/src/core/configuration/popup.js new file mode 100644 index 0000000..2be2423 --- /dev/null +++ b/src/core/configuration/popup.js @@ -0,0 +1,57 @@ +/** + * @typedef {'auto' | 'mobile' | 'desktop'} PopupDevice + * @description Runtime device override for popup loading. + */ + +/** + * @class Popup + * @classdesc Configuration for dashboard popups. + * @property {String} id - The popup id. + * @property {String} container - The container to append the popup to, defaults to 'body'. + * @property {PopupDevice} device - Runtime device preference, defaults to 'auto'. + */ +class Popup { + static _id + static _container = 'body' + static _device = 'auto' + + static set id(value) { + this._id = value + } + + static get id() { + return this._id + } + + static set container(value) { + this._container = value + } + + static get container() { + return this._container + } + + static set device(value) { + if (!['auto', 'mobile', 'desktop'].includes(value)) { + throw new Error(`Invalid popup device value: ${value}`) + } + + this._device = value + } + + static get device() { + return this._device + } + + static assign(props) { + if (props) { + Object.entries(props).forEach(([key, value]) => { + this[key] = value + }) + } + + return this + } +} + +export { Popup } diff --git a/src/core/event.js b/src/core/event.js index 47fdf98..c83e660 100644 --- a/src/core/event.js +++ b/src/core/event.js @@ -6,6 +6,9 @@ export default class Event { 'utm-set', 'forms:collected', 'form:completed', + 'popup:mounted', + 'popup:opened', + 'popup:closed', 'alert:shown', 'alert:dismissed', 'alert:accepted', diff --git a/src/hellotext.js b/src/hellotext.js index c5add34..d428c9d 100644 --- a/src/hellotext.js +++ b/src/hellotext.js @@ -7,6 +7,7 @@ import { Fingerprint, FormCollection, Page, + Popup, Push, Query, Session, @@ -21,10 +22,12 @@ class Hellotext { static eventEmitter = new Event() static forms static business + static popup static webchat static whatsapp static push static alert + static initializationVersion = 0 /** * initialize the module. @@ -32,6 +35,10 @@ class Hellotext { * @param { Configuration } config */ static async initialize(business, config = {}) { + const initializationVersion = ++this.initializationVersion + this.popup?.unmount?.() + this.popup = undefined + this.alert?.dispose() this.alert = null this.push?.dispose() @@ -63,6 +70,11 @@ class Hellotext { } } + const popupConfig = + config.popup === false + ? false + : this.deepMergePlainObjects((businessData && businessData.popup) || {}, config.popup || {}) + const webchatConfig = config.webchat === false ? false @@ -84,16 +96,53 @@ class Hellotext { Object.prototype.hasOwnProperty.call(config.webchat, 'behaviour') Configuration.webchat.behaviourOverride = hasExplicitBehaviourOverride + const widgetLoads = [] + if (webchatConfig && webchatConfig.id) { Configuration.webchat.assign(webchatConfig) - this.webchat = await Webchat.load(webchatConfig.id) + widgetLoads.push( + Webchat.load(webchatConfig.id).then(webchat => { + if (this.business === businessContext) this.webchat = webchat + }), + ) } if (whatsappConfig && whatsappConfig.id) { Configuration.whatsapp.assign(whatsappConfig) - this.whatsapp = await WhatsAppWidget.load(whatsappConfig.id) + widgetLoads.push( + WhatsAppWidget.load(whatsappConfig.id).then(whatsapp => { + if (this.business === businessContext) this.whatsapp = whatsapp + }), + ) } + if (popupConfig && popupConfig.id) { + const resolvedPopupConfig = { container: 'body', device: 'auto', ...popupConfig } + Configuration.popup.assign(resolvedPopupConfig) + widgetLoads.push( + Popup.load(resolvedPopupConfig.id, { + container: resolvedPopupConfig.container, + shouldMount: () => { + return ( + this.business === businessContext && + this.initializationVersion === initializationVersion + ) + }, + }).then(popup => { + if ( + this.business === businessContext && + this.initializationVersion === initializationVersion + ) { + this.popup = popup + } + }), + ) + } + + await Promise.all(widgetLoads) + if (this.business !== businessContext || this.initializationVersion !== initializationVersion) + return + if (typeof MutationObserver !== 'undefined') { this.forms.collectExistingFormsOnPage() } diff --git a/src/index.js b/src/index.js index f84b431..429d53e 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,7 @@ import Hellotext from './hellotext' import AlertController from './controllers/alert_controller' import FormController from './controllers/form_controller' import MessageController from './controllers/message_controller' +import PopupController from './controllers/popup_controller' import WebChatEmojiController from './controllers/webchat/emoji_picker_controller' import WebchatController from './controllers/webchat_controller' @@ -11,6 +12,7 @@ const application = Application.start() application.register('hellotext--alert', AlertController) application.register('hellotext--form', FormController) +application.register('hellotext--popup', PopupController) application.register('hellotext--webchat', WebchatController) application.register('hellotext--webchat--emoji', WebChatEmojiController) application.register('hellotext--message', MessageController) diff --git a/src/models/business.js b/src/models/business.js index 4221147..6548d0d 100644 --- a/src/models/business.js +++ b/src/models/business.js @@ -26,7 +26,9 @@ const stylesheetLoadTimeout = 10000 * @property {Object} [features] - Feature flags enabled for the business. * @property {String} [locale] - Default dashboard locale for the business. * @property {String} [style_url] - Stylesheet URL to inject for dashboard-managed surfaces. + * @property {{id: String}|null} [popup] - Dashboard popup defaults. * @property {BusinessWebchat|null} [webchat] - Dashboard webchat defaults. + * @property {{id: String}|null} [whatsapp] - Dashboard WhatsApp widget defaults. * @property {{public_key: String}|null} [push] - Push public key. * @property {{html: String}|null} [alert] - Smart Alert HTML. * @property {String|Array} [whitelist] - Domain whitelist configuration. diff --git a/src/models/index.js b/src/models/index.js index 146c091..8e4d661 100644 --- a/src/models/index.js +++ b/src/models/index.js @@ -5,6 +5,7 @@ export { Fingerprint } from './fingerprint' export { Form } from './form' export { FormCollection } from './form_collection' export { Page } from './page' +export { Popup } from './popup' export { Push } from './push' export { Query } from './query' export { Session } from './session' diff --git a/src/models/popup.js b/src/models/popup.js new file mode 100644 index 0000000..0c58623 --- /dev/null +++ b/src/models/popup.js @@ -0,0 +1,69 @@ +import { Configuration } from '../core' + +import API from '../api' + +class Popup { + static async load(id, options = {}) { + const popup = new Popup( + { + id, + html: await API.popups.get(id), + }, + options, + ) + + popup.rendered = popup.render() + + return popup + } + + constructor(data, { container = Configuration.popup.container, shouldMount = () => true } = {}) { + this.data = data + this.container = container + this.mounted = false + this.rendered = Promise.resolve(false) + this.shouldMount = shouldMount + } + + async render() { + if (!this.data.html || !this.shouldMount()) return false + + const container = this.containerToAppendTo + if (!container) { + console.warn( + `Hellotext popup was not mounted because the container ${this.container} was not found.`, + ) + return false + } + + if (!this.shouldMount()) return false + + container.appendChild(this.data.html) + this.mounted = true + + if (!this.shouldMount()) this.unmount() + + return this.mounted + } + + /** + * Remove this popup's server-rendered surface when a later initialization + * replaces or disables it. Removing the root also disconnects Stimulus. + * + * @returns {void} + */ + unmount() { + this.data.html?.remove() + this.mounted = false + } + + get containerToAppendTo() { + try { + return document.querySelector(this.container) + } catch (_) { + return null + } + } +} + +export { Popup } diff --git a/src/models/webchat.js b/src/models/webchat.js index 5a5115f..4234e21 100644 --- a/src/models/webchat.js +++ b/src/models/webchat.js @@ -24,7 +24,7 @@ class Webchat { async render() { this.applyBehaviourOverride() - if (!await this.stylesheetLoaded) { + if (!(await this.stylesheetLoaded)) { console.warn('Hellotext webchat was not mounted because its stylesheet failed to load.') return false } diff --git a/src/models/whatsapp_widget.js b/src/models/whatsapp_widget.js index 9ecabe6..8721339 100644 --- a/src/models/whatsapp_widget.js +++ b/src/models/whatsapp_widget.js @@ -7,7 +7,7 @@ class WhatsAppWidget { static async load(id) { const widget = new WhatsAppWidget({ id, - html: await API.whatsappWidgets.get(id) + html: await API.whatsappWidgets.get(id), }) widget.rendered = widget.render() @@ -26,12 +26,16 @@ class WhatsAppWidget { const container = this.containerToAppendTo if (!container) { - console.warn(`Hellotext WhatsApp widget was not mounted because the container ${Configuration.whatsapp.container} was not found.`) + console.warn( + `Hellotext WhatsApp widget was not mounted because the container ${Configuration.whatsapp.container} was not found.`, + ) return false } - if (!await this.stylesheetLoaded) { - console.warn('Hellotext WhatsApp widget was not mounted because its stylesheet failed to load.') + if (!(await this.stylesheetLoaded)) { + console.warn( + 'Hellotext WhatsApp widget was not mounted because its stylesheet failed to load.', + ) return false }