Bubble API & Events
The <saytv-chat-bubble> element exposes a JavaScript API for programmatic control and dispatches custom events for integration with your application.
JavaScript API
Access the bubble element and call methods directly:
const bubble = document.querySelector('saytv-chat-bubble');Methods
| Method | Description |
|---|---|
open() | Open the chat panel. Creates the inner widget on first call. |
close() | Close the chat panel with a closing animation. |
toggle() | Toggle the panel open/closed. |
show() | Show the bubble (after it was hidden). |
hide() | Hide the entire bubble (launcher + panel). Closes the panel first if open. |
destroy() | Full teardown - removes the inner widget, clears timers, and removes event listeners. |
setUsername(name) | Rename the signed-in user. Returns a Promise<User>. |
Properties
| Property | Description |
|---|---|
translations (setter) | Supply translation catalogs keyed by locale. Registered globally, so the launcher's own chrome (tooltip, aria-labels) is localized too, and forwarded to the inner widget. |
bubble.translations = {
fr: { 'bubble.openChat': 'Ouvrir la discussion' },
};Unlike <saytv-chat>, the bubble renders chrome of its own before the widget exists, which is why setting this on the bubble covers both. For a hosted catalog use the translations-url attribute instead. See Localization.
open()
Opens the chat panel. If this is the first time opening, the inner <saytv-chat> widget is created and mounted. Subsequent calls show the existing widget without re-creating it.
bubble.open();The greeting tooltip is automatically dismissed when the panel opens. The unread badge is reset to zero.
close()
Closes the chat panel with a slide-down animation (200ms). Focus returns to the launcher button.
bubble.close();toggle()
Convenience method that opens the panel if closed, or closes it if open.
bubble.toggle();show() / hide()
Control the visibility of the entire bubble element. hide() closes the panel first if it's open.
// Hide the bubble when user navigates to a page where chat isn't needed
bubble.hide();
// Show it again
bubble.show();Internally, hide() sets the hidden attribute and show() removes it.
setUsername()
Renames the signed-in user. Available on both <saytv-chat-bubble> and <saytv-chat> — see Methods for the full reference.
try {
const user = await bubble.setUsername('new-name');
console.log('Renamed to', user.username);
} catch (err) {
// err.errors.username explains why: taken, too short, or blocked by moderation
console.error(err.errors?.username?.[0] ?? err.message);
}The JWT username claim is applied when the account is first created, and re-authenticating with a changed claim syncs it. Use this method when your app lets users rename themselves mid-session, or when you need to handle the rejection reason yourself.
Usernames must be at least 3 characters, unique across the tenant, and pass moderation. A name held by a deleted account is released and can be reused.
WARNING
Guests cannot rename themselves. Calling this on a guest session rejects with a 403.
destroy()
Tears down the bubble completely. Removes the inner widget, clears greeting and animation timers, and removes all event listeners (keyboard, outside click, mobile detection).
bubble.destroy();WARNING
After calling destroy(), the bubble element remains in the DOM but is inert. Remove it from the DOM if you no longer need it:
bubble.destroy();
bubble.remove();Events
The bubble dispatches its own events prefixed with saytv:. These are standard CustomEvent objects that bubble up through the DOM.
| Event | Fired When | Detail |
|---|---|---|
saytv:open | Chat panel opens | - |
saytv:close | Chat panel closes | - |
saytv:ready | Inner <saytv-chat> widget fires stv:ready | - |
saytv:unread | Unread message count changes | { count: number } |
Listening to Events
const bubble = document.querySelector('saytv-chat-bubble');
bubble.addEventListener('saytv:open', () => {
console.log('Chat opened');
});
bubble.addEventListener('saytv:close', () => {
console.log('Chat closed');
});
bubble.addEventListener('saytv:ready', () => {
console.log('Chat widget is ready');
});
bubble.addEventListener('saytv:unread', (e) => {
console.log('Unread messages:', e.detail.count);
});Unread Badge
The bubble tracks incoming messages via the inner widget's stv:message-received event. When the panel is closed, each new message increments the unread count and the saytv:unread event fires. When the panel opens, the count resets to zero.
bubble.addEventListener('saytv:unread', (e) => {
// Update your page title or favicon
document.title = e.detail.count > 0
? `(${e.detail.count}) My Site`
: 'My Site';
});The badge displays counts up to 99 and shows "99+" for higher values.
Inner Widget Events
All standard <saytv-chat> events (stv:ready, stv:message-sent, stv:user-login, etc.) are dispatched on the inner widget element inside the shadow DOM. They do not automatically propagate to the bubble element.
To listen for inner widget events, use the saytv:ready event to know when the widget exists, then access it:
bubble.addEventListener('saytv:ready', () => {
// The inner widget is now available in the shadow DOM
const widget = bubble.shadowRoot.querySelector('saytv-chat');
widget.addEventListener('stv:message-sent', (e) => {
console.log('Message sent:', e.detail.message);
});
widget.addEventListener('stv:user-login', (e) => {
console.log('User logged in:', e.detail.user);
});
});Common Patterns
Open bubble from a custom button
Use the JavaScript API to trigger the bubble from anywhere in your page:
<button id="help-btn">Need Help?</button>
<saytv-chat-bubble app-id="your-app-id"></saytv-chat-bubble>
<script>
document.getElementById('help-btn').addEventListener('click', () => {
document.querySelector('saytv-chat-bubble').open();
});
</script>Hide on specific pages
const bubble = document.querySelector('saytv-chat-bubble');
// Hide on checkout pages
if (location.pathname.startsWith('/checkout')) {
bubble.hide();
}Analytics integration
bubble.addEventListener('saytv:open', () => {
analytics.track('chat_bubble_opened');
});
bubble.addEventListener('saytv:close', () => {
analytics.track('chat_bubble_closed');
});
bubble.addEventListener('saytv:unread', (e) => {
if (e.detail.count > 0) {
analytics.track('chat_unread_message', { count: e.detail.count });
}
});Conditionally show based on user state
const bubble = document.querySelector('saytv-chat-bubble');
// Only show for logged-in users
if (!currentUser) {
bubble.hide();
}
// Show after a delay for first-time visitors
if (isFirstVisit) {
setTimeout(() => bubble.show(), 10000);
}