@cognigy/chat-components-vue 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/chat-components-vue.css +1 -1
  2. package/dist/chat-components-vue.js +3730 -3634
  3. package/dist/components/Message.vue.d.ts +4 -0
  4. package/dist/components/common/Typography.vue.d.ts +1 -1
  5. package/dist/components/messages/ListItem.vue.d.ts +1 -1
  6. package/dist/composables/useLiveRegion.d.ts +30 -0
  7. package/dist/index.d.ts +3 -2
  8. package/dist/types/index.d.ts +55 -0
  9. package/dist/utils/theme.d.ts +12 -1
  10. package/package.json +6 -2
  11. package/src/components/Message.vue +85 -53
  12. package/src/components/common/ActionButton.vue +16 -7
  13. package/src/components/common/ChatBubble.vue +8 -6
  14. package/src/components/common/ChatEvent.vue +5 -2
  15. package/src/components/common/TypingIndicator.vue +4 -1
  16. package/src/components/common/Typography.vue +56 -67
  17. package/src/components/messages/AudioMessage.vue +4 -1
  18. package/src/components/messages/DatePicker.vue +5 -27
  19. package/src/components/messages/FileMessage.vue +12 -3
  20. package/src/components/messages/Gallery.vue +96 -10
  21. package/src/components/messages/GalleryItem.vue +17 -5
  22. package/src/components/messages/ImageMessage.vue +20 -5
  23. package/src/components/messages/List.vue +56 -42
  24. package/src/components/messages/ListItem.vue +105 -68
  25. package/src/components/messages/TextMessage.vue +1 -1
  26. package/src/components/messages/TextWithButtons.vue +35 -11
  27. package/src/components/messages/VideoMessage.vue +35 -26
  28. package/src/composables/useLiveRegion.ts +101 -0
  29. package/src/index.ts +4 -1
  30. package/src/types/index.ts +71 -0
  31. package/src/utils/theme.ts +36 -1
@@ -3,6 +3,10 @@ declare const _default: import('vue').DefineComponent<MessageProps, {}, {}, {},
3
3
  action: import('..').MessageSender;
4
4
  config: import('..').ChatConfig;
5
5
  onEmitAnalytics: (event: string, payload?: unknown) => void;
6
+ openXAppOverlay: (url: string | undefined) => void;
7
+ onImageClick: (url: string) => void;
8
+ messageParams: import('..').MessageParams;
9
+ onSlideChange: (index: number, total: number) => void;
6
10
  theme: import('..').ChatTheme;
7
11
  prevMessage: import('@cognigy/socket-client').IMessage;
8
12
  disableHeader: boolean;
@@ -1,5 +1,5 @@
1
1
  import { CSSProperties } from 'vue';
2
- export type TagVariant = 'h1-semibold' | 'h2-regular' | 'h2-semibold' | 'title1-semibold' | 'title1-regular' | 'title2-semibold' | 'title2-regular' | 'body-regular' | 'body-semibold' | 'copy-medium' | 'cta-semibold';
2
+ export type TagVariant = "h1-semibold" | "h2-regular" | "h2-semibold" | "title1-semibold" | "title1-regular" | "title2-semibold" | "title2-regular" | "body-regular" | "body-semibold" | "copy-medium" | "cta-semibold";
3
3
  interface Props {
4
4
  variant?: TagVariant;
5
5
  component?: string;
@@ -2,7 +2,7 @@ import { IWebchatAttachmentElement } from '../../types';
2
2
  interface Props {
3
3
  element: IWebchatAttachmentElement;
4
4
  isHeaderElement?: boolean;
5
- headingLevel?: 'h4' | 'h5';
5
+ headingLevel?: "h4" | "h5";
6
6
  id: string;
7
7
  dividerBefore?: boolean;
8
8
  dividerAfter?: boolean;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * useLiveRegion composable
3
+ *
4
+ * Manages an ARIA live region for screen reader announcements.
5
+ * Creates/reuses a single visually-hidden <div role="status" aria-live="polite"> element
6
+ * in the DOM, and announces content when messages arrive.
7
+ *
8
+ * This matches the React reference implementation's approach to accessibility
9
+ * announcements for message components.
10
+ */
11
+ interface UseLiveRegionOptions {
12
+ /** Text to announce to screen readers */
13
+ announcement: string;
14
+ /** Whether the announcement should be made (defaults to true) */
15
+ enabled?: boolean;
16
+ }
17
+ /**
18
+ * Composable that announces content to screen readers via an ARIA live region.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * // In a message component:
23
+ * useLiveRegion({
24
+ * announcement: `New message: ${messageText}`,
25
+ * enabled: !props.ignoreLiveRegion,
26
+ * })
27
+ * ```
28
+ */
29
+ export declare function useLiveRegion(options: UseLiveRegionOptions): void;
30
+ export {};
package/dist/index.d.ts CHANGED
@@ -24,10 +24,11 @@ export { useSanitize } from './composables/useSanitize';
24
24
  export { useImageContext, provideImageContext, ImageContextKey } from './composables/useImageContext';
25
25
  export { useChannelPayload, type UseChannelPayloadReturn } from './composables/useChannelPayload';
26
26
  export { useCollation, type UseCollationReturn, type CollatedMessage } from './composables/useCollation';
27
+ export { useLiveRegion } from './composables/useLiveRegion';
27
28
  export { DownloadIcon, CloseIcon, VideoPlayIcon, AudioPlayIcon, AudioPauseIcon, ArrowBackIcon, LinkIcon } from './assets/svg';
28
29
  export { match, getChannelPayload } from './utils/matcher';
29
30
  export { sanitizeHTMLWithConfig, sanitizeContent } from './utils/sanitize';
30
- export { configColorsToCssVariables } from './utils/theme';
31
+ export { configColorsToCssVariables, themeFontToCssVariable } from './utils/theme';
31
32
  export { getWebchatButtonLabel, interpolateString, getRandomId, moveFocusToMessageFocusTarget, replaceUrlsWithHTMLanchorElem, getBackgroundImage, getFileName, getFileExtension, getSizeLabel, isImageAttachment, VALID_IMAGE_MIME_TYPES } from './utils/helpers';
32
- export type { ChatConfig, ChatSettings, ChatTheme, MessageProps, MessageSender, MessagePlugin, MessagePluginOptions, MessageContext, IMessage, IWebchatButton, IWebchatQuickReply, IWebchatTemplateAttachment, IWebchatAttachmentElement, IWebchatAudioAttachment, IWebchatImageAttachment, IWebchatVideoAttachment, IUploadFileAttachmentData, IDatePickerData, IWebchatChannelPayload, } from './types';
33
+ export type { ChatConfig, ChatSettings, ChatTheme, MessageProps, MessageSender, MessagePlugin, MessagePluginOptions, MessageContext, MessageParams, IMessage, IStreamingMessage, IWebchatButton, IWebchatQuickReply, IWebchatTemplateAttachment, IWebchatAttachmentElement, IWebchatAudioAttachment, IWebchatImageAttachment, IWebchatVideoAttachment, IUploadFileAttachmentData, IDatePickerData, IWebchatChannelPayload, } from './types';
33
34
  export type { TagVariant } from './components/common/Typography.vue';
@@ -1,5 +1,23 @@
1
1
  import { IMessage, IWebchatQuickReply as QuickReply, IWebchatTemplateAttachment as TemplateAttachment, IWebchatAudioAttachment as AudioAttachment, IWebchatImageAttachment as ImageAttachment, IWebchatVideoAttachment as VideoAttachment } from '@cognigy/socket-client';
2
2
  import { Component } from 'vue';
3
+ /**
4
+ * Parameters about the current message state in the conversation.
5
+ * Used to control interactive element behavior (e.g., disabling quick replies after user reply).
6
+ */
7
+ export interface MessageParams {
8
+ /** Whether the user has already replied to this message (disables quick reply buttons) */
9
+ hasReply?: boolean;
10
+ /** Whether the conversation has ended (disables all interactive elements) */
11
+ isConversationEnded?: boolean;
12
+ }
13
+ /**
14
+ * Extended IMessage with streaming animation state.
15
+ * Used with progressive message rendering to conditionally show/hide elements
16
+ * based on whether the message is still being streamed.
17
+ */
18
+ export interface IStreamingMessage extends IMessage {
19
+ animationState?: 'start' | 'animating' | 'done';
20
+ }
3
21
  /**
4
22
  * Extended IMessage with optional runtime properties not in base interface.
5
23
  * The socket-client IMessage doesn't include 'id', but it may be present at runtime.
@@ -61,6 +79,13 @@ export interface ChatSettings {
61
79
  disableUrlButtonSanitization?: boolean;
62
80
  dynamicImageAspectRatio?: boolean;
63
81
  showEngagementInChat?: boolean;
82
+ /**
83
+ * Gallery layout variant:
84
+ * - 'default': Standard gallery with 206px wide slides
85
+ * - 'compact': Narrower slides with smaller navigation buttons
86
+ * - 'copilot': Adaptive sizing based on card count (1-card, 2-card, 3+ layouts)
87
+ */
88
+ galleryVariant?: 'default' | 'compact' | 'copilot';
64
89
  };
65
90
  colors?: {
66
91
  primaryColor?: string;
@@ -78,11 +103,19 @@ export interface ChatSettings {
78
103
  borderBotMessage?: string;
79
104
  borderUserMessage?: string;
80
105
  borderAgentMessage?: string;
106
+ borderMediaCard?: string;
107
+ listDividerColor?: string;
81
108
  textLinkColor?: string;
82
109
  adaptiveCardTextColor?: string;
83
110
  adaptiveCardInputColor?: string;
84
111
  adaptiveCardInputBackground?: string;
85
112
  adaptiveCardInputBorder?: string;
113
+ filePreviewBackground?: string;
114
+ filePreviewTextColor?: string;
115
+ filePreviewSecondaryTextColor?: string;
116
+ chatEventBackground?: string;
117
+ chatEventTextColor?: string;
118
+ messageShadow?: string;
86
119
  };
87
120
  behavior?: {
88
121
  renderMarkdown?: boolean;
@@ -99,6 +132,12 @@ export interface ChatSettings {
99
132
  * Use `false`/omit for interactive chat interfaces with smart auto-detection.
100
133
  */
101
134
  adaptiveCardsReadonly?: boolean;
135
+ /**
136
+ * When true, buttons/quick replies are hidden while a message is still being streamed.
137
+ * Works with IStreamingMessage.animationState to delay showing interactive elements
138
+ * until the text animation is complete.
139
+ */
140
+ progressiveMessageRendering?: boolean;
102
141
  };
103
142
  widgetSettings?: {
104
143
  enableDefaultPreview?: boolean;
@@ -187,6 +226,14 @@ export interface MessageProps {
187
226
  disableHeader?: boolean;
188
227
  plugins?: MessagePlugin[];
189
228
  onEmitAnalytics?: (event: string, payload?: unknown) => void;
229
+ /** Conversation state parameters (hasReply, isConversationEnded) */
230
+ messageParams?: MessageParams;
231
+ /** Callback to open an xApp overlay (for openXApp button type) */
232
+ openXAppOverlay?: (url: string | undefined) => void;
233
+ /** Callback when gallery slide changes — receives (activeIndex, totalSlides) */
234
+ onSlideChange?: (index: number, total: number) => void;
235
+ /** Callback when an image is clicked — receives the image URL */
236
+ onImageClick?: (url: string) => void;
190
237
  }
191
238
  /**
192
239
  * Options for match rules and plugins
@@ -243,6 +290,14 @@ export interface MessageContext {
243
290
  theme?: ChatTheme;
244
291
  action?: MessageSender;
245
292
  onEmitAnalytics?: (event: string, payload?: unknown) => void;
293
+ /** Conversation state parameters (hasReply, isConversationEnded) */
294
+ messageParams?: MessageParams;
295
+ /** Callback to open an xApp overlay (for openXApp button type) */
296
+ openXAppOverlay?: (url: string | undefined) => void;
297
+ /** Callback when gallery slide changes — receives (activeIndex, totalSlides) */
298
+ onSlideChange?: (index: number, total: number) => void;
299
+ /** Callback when an image is clicked — receives the image URL */
300
+ onImageClick?: (url: string) => void;
246
301
  }
247
302
  /**
248
303
  * DatePicker plugin data interface
@@ -1,4 +1,4 @@
1
- import { ChatSettings } from '../types';
1
+ import { ChatSettings, ChatTheme } from '../types';
2
2
  /**
3
3
  * Maps config.settings.colors to CSS custom properties.
4
4
  * Returns an object suitable for use as an inline style that sets CSS variables.
@@ -16,3 +16,14 @@ import { ChatSettings } from '../types';
16
16
  * ```
17
17
  */
18
18
  export declare function configColorsToCssVariables(colors?: ChatSettings['colors']): Record<string, string>;
19
+ /**
20
+ * Maps a ChatTheme's fontFamily to the --cc-font-family CSS variable.
21
+ * Consumers can set this to override the default Figtree font.
22
+ *
23
+ * Components should reference font-family as:
24
+ * font-family: var(--cc-font-family, 'Figtree', sans-serif);
25
+ *
26
+ * @param theme - The ChatTheme object
27
+ * @returns A record with the --cc-font-family CSS variable, or empty object
28
+ */
29
+ export declare function themeFontToCssVariable(theme?: ChatTheme): Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cognigy/chat-components-vue",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Vue 3 version of @cognigy/chat-components",
6
6
  "license": "MIT",
@@ -52,6 +52,9 @@
52
52
  "test:watch": "vitest",
53
53
  "test:ui": "vitest --ui",
54
54
  "test:coverage": "vitest run --coverage",
55
+ "test:e2e": "playwright test",
56
+ "test:e2e:update": "playwright test --update-snapshots",
57
+ "test:e2e:ui": "playwright test --ui",
55
58
  "lint": "eslint .",
56
59
  "type-check": "vue-tsc --noEmit",
57
60
  "prepare": "npm run build"
@@ -71,6 +74,7 @@
71
74
  "swiper": "^11.2.10"
72
75
  },
73
76
  "devDependencies": {
77
+ "@playwright/test": "^1.58.2",
74
78
  "@types/markdown-it": "^14.1.2",
75
79
  "@types/node": "^25.0.9",
76
80
  "@vitejs/plugin-vue": "^6.0.0",
@@ -79,7 +83,7 @@
79
83
  "eslint": "^9.0.0",
80
84
  "eslint-plugin-vue": "^10.7.0",
81
85
  "globals": "^17.0.0",
82
- "jsdom": "^27.0.0",
86
+ "jsdom": "^28.0.0",
83
87
  "npm-check-updates": "^19.3.1",
84
88
  "typescript": "^5.9.0",
85
89
  "typescript-eslint": "^8.53.1",
@@ -22,26 +22,32 @@
22
22
  </template>
23
23
 
24
24
  <script setup lang="ts">
25
- import { computed, reactive, watchEffect, useCssModule, type Component } from 'vue'
26
- import { match } from '../utils/matcher'
27
- import { configColorsToCssVariables } from '../utils/theme'
28
- import { provideMessageContext } from '../composables/useMessageContext'
29
- import { getMessageId, isMessagePlugin } from '../types'
30
- import type { MessageProps, MessageContext } from '../types'
31
-
32
- const $style = useCssModule()
25
+ import {
26
+ computed,
27
+ reactive,
28
+ watchEffect,
29
+ useCssModule,
30
+ type Component,
31
+ } from "vue";
32
+ import { match } from "../utils/matcher";
33
+ import {configColorsToCssVariables, themeFontToCssVariable} from "../utils/theme";
34
+ import { provideMessageContext } from "../composables/useMessageContext";
35
+ import { getMessageId, isMessagePlugin } from "../types";
36
+ import type { MessageProps, MessageContext } from "../types";
37
+
38
+ const $style = useCssModule();
33
39
 
34
40
  // Import all message type components
35
- import TextMessage from './messages/TextMessage.vue'
36
- import ImageMessage from './messages/ImageMessage.vue'
37
- import VideoMessage from './messages/VideoMessage.vue'
38
- import AudioMessage from './messages/AudioMessage.vue'
39
- import TextWithButtons from './messages/TextWithButtons.vue'
40
- import Gallery from './messages/Gallery.vue'
41
- import List from './messages/List.vue'
42
- import FileMessage from './messages/FileMessage.vue'
43
- import DatePicker from './messages/DatePicker.vue'
44
- import AdaptiveCard from './messages/AdaptiveCard.vue'
41
+ import TextMessage from "./messages/TextMessage.vue";
42
+ import ImageMessage from "./messages/ImageMessage.vue";
43
+ import VideoMessage from "./messages/VideoMessage.vue";
44
+ import AudioMessage from "./messages/AudioMessage.vue";
45
+ import TextWithButtons from "./messages/TextWithButtons.vue";
46
+ import Gallery from "./messages/Gallery.vue";
47
+ import List from "./messages/List.vue";
48
+ import FileMessage from "./messages/FileMessage.vue";
49
+ import DatePicker from "./messages/DatePicker.vue";
50
+ import AdaptiveCard from "./messages/AdaptiveCard.vue";
45
51
 
46
52
  const props = withDefaults(defineProps<MessageProps>(), {
47
53
  action: undefined,
@@ -51,10 +57,14 @@ const props = withDefaults(defineProps<MessageProps>(), {
51
57
  plugins: undefined,
52
58
  onEmitAnalytics: undefined,
53
59
  disableHeader: false,
54
- })
60
+ messageParams: undefined,
61
+ openXAppOverlay: undefined,
62
+ onSlideChange: undefined,
63
+ onImageClick: undefined,
64
+ });
55
65
 
56
66
  // Generate a unique message ID for accessibility
57
- const dataMessageId = computed(() => getMessageId(props.message))
67
+ const dataMessageId = computed(() => getMessageId(props.message));
58
68
 
59
69
  // Provide message context for child components
60
70
  // Using reactive object synced via watchEffect for proper reactivity tracking
@@ -63,7 +73,11 @@ const messageContext = reactive<MessageContext>({
63
73
  config: props.config,
64
74
  action: props.action,
65
75
  onEmitAnalytics: props.onEmitAnalytics,
66
- })
76
+ messageParams: props.messageParams,
77
+ openXAppOverlay: props.openXAppOverlay,
78
+ onSlideChange: props.onSlideChange,
79
+ onImageClick: props.onImageClick,
80
+ });
67
81
 
68
82
  // Keep context in sync with props changes
69
83
  watchEffect(() => {
@@ -71,61 +85,80 @@ watchEffect(() => {
71
85
  messageContext.config = props.config
72
86
  messageContext.action = props.action
73
87
  messageContext.onEmitAnalytics = props.onEmitAnalytics
74
- })
88
+ messageContext.messageParams = props.messageParams
89
+ messageContext.openXAppOverlay = props.openXAppOverlay
90
+ messageContext.onSlideChange = props.onSlideChange
91
+ messageContext.onImageClick = props.onImageClick
92
+ });
75
93
 
76
- provideMessageContext(messageContext)
94
+ provideMessageContext(messageContext);
77
95
 
78
96
  // Component map for internal match rules (maps rule names to Vue components)
79
97
  const componentMap: Record<string, Component> = {
80
- 'Text': TextMessage,
81
- 'Image': ImageMessage,
82
- 'Video': VideoMessage,
83
- 'Audio': AudioMessage,
84
- 'TextWithButtons': TextWithButtons,
85
- 'Gallery': Gallery,
86
- 'List': List,
87
- 'File': FileMessage,
88
- 'DatePicker': DatePicker,
89
- 'AdaptiveCard': AdaptiveCard,
90
- }
98
+ Text: TextMessage,
99
+ Image: ImageMessage,
100
+ Video: VideoMessage,
101
+ Audio: AudioMessage,
102
+ TextWithButtons: TextWithButtons,
103
+ Gallery: Gallery,
104
+ List: List,
105
+ File: FileMessage,
106
+ DatePicker: DatePicker,
107
+ AdaptiveCard: AdaptiveCard,
108
+ };
91
109
 
92
110
  // Match message to appropriate components
93
111
  const matchedComponents = computed(() => {
94
- const matched = match(props.message, props.config, props.plugins)
112
+ const matched = match(props.message, props.config, props.plugins);
95
113
 
96
114
  if (!Array.isArray(matched) || matched.length < 1) {
97
- return []
115
+ return [];
98
116
  }
99
117
 
100
118
  // Resolve components from match results
101
119
  return matched
102
- .map(rule => {
120
+ .map((rule) => {
103
121
  // External plugins (MessagePlugin) provide their own component
104
122
  if (isMessagePlugin(rule)) {
105
- return rule.component
123
+ return rule.component;
106
124
  }
107
125
  // Internal rules (MatchRule) use name lookup in componentMap
108
- return componentMap[rule.name] ?? null
126
+ return componentMap[rule.name] ?? null;
109
127
  })
110
- .filter((c): c is Component => c !== null)
128
+ .filter((c): c is Component => c !== null);
129
+ });
130
+
131
+ // Compute direction based on source + config mapping
132
+ const directionMapping = computed(() => props.config?.settings?.widgetSettings?.sourceDirectionMapping)
133
+
134
+ const messageDirection = computed(() => {
135
+ const source = props.message.source
136
+ if (source === 'user') return directionMapping.value?.user || 'outgoing'
137
+ if (source === 'bot') return directionMapping.value?.bot || 'incoming'
138
+ if (source === 'agent') return directionMapping.value?.agent || 'incoming'
139
+ return 'incoming'
111
140
  })
112
141
 
113
142
  // Root element classes
114
143
  const rootClasses = computed(() => {
115
144
  return [
116
145
  'webchat-message-row',
117
- props.message.source,
146
+ props.message.source, // keep global source class (for ChatBubble color selectors)
118
147
  $style.message,
119
- props.message.source === 'bot' && $style.bot,
120
- props.message.source === 'user' && $style.user,
121
- props.message.source === 'agent' && $style.agent,
148
+ props.message.source === "bot" && $style.bot,
149
+ props.message.source === "user" && $style.user,
150
+ props.message.source === "agent" && $style.agent,
151
+ $style[messageDirection.value], // incoming or outgoing alignment
122
152
  ].filter(Boolean)
123
153
  })
124
154
 
125
- // CSS variable injection from config colors
155
+ // CSS variable injection from config colors and theme font
126
156
  const cssVariableStyle = computed(() => {
127
- return configColorsToCssVariables(props.config?.settings?.colors)
128
- })
157
+ return {
158
+ ...configColorsToCssVariables(props.config?.settings?.colors),
159
+ ...themeFontToCssVariable(props.theme),
160
+ }
161
+ });
129
162
  </script>
130
163
 
131
164
  <style module>
@@ -134,20 +167,19 @@ const cssVariableStyle = computed(() => {
134
167
  flex-direction: column;
135
168
  gap: 8px;
136
169
  margin-bottom: 16px;
170
+ font-family: var(--cc-font-family, 'Figtree', sans-serif);
171
+ padding-left: 56px;
172
+ padding-right: 16px;
137
173
  }
138
174
 
139
- .message.bot {
175
+ .message.incoming {
140
176
  align-items: flex-start;
141
177
  }
142
178
 
143
- .message.user {
179
+ .message.outgoing {
144
180
  align-items: flex-end;
145
181
  }
146
182
 
147
- .message.agent {
148
- align-items: flex-start;
149
- }
150
-
151
183
  /* Screen reader only */
152
184
  .srOnly {
153
185
  position: absolute;
@@ -282,7 +282,7 @@ a.button {
282
282
  gap: 10px;
283
283
  text-decoration: none;
284
284
  background: var(--cc-primary-color);
285
- color: var(--cc-primary-contrast-color);
285
+ color: var(--cc-primary-contrast-color, #ffffff);
286
286
  border: none;
287
287
  outline: none;
288
288
  position: relative;
@@ -296,7 +296,7 @@ button.button svg,
296
296
  a.button svg,
297
297
  button.button path,
298
298
  a.button svg path {
299
- fill: var(--cc-primary-contrast-color);
299
+ fill: var(--cc-primary-contrast-color, #ffffff);
300
300
  width: 12px;
301
301
  }
302
302
 
@@ -304,16 +304,25 @@ button.button:hover,
304
304
  a.button:hover,
305
305
  button.button:focus,
306
306
  a.button:focus {
307
- background: var(--cc-primary-color-hover);
307
+ background: var(--cc-primary-color-hover, color-mix(in srgb, var(--cc-primary-color), black 15%));
308
308
  }
309
309
 
310
310
  /* Explicitly increase the specificity of the :focus-visible selector */
311
311
  [data-cognigy-webchat-root] button.button:focus-visible,
312
312
  [data-cognigy-webchat-root] a.button:focus-visible,
313
313
  [data-cognigy-webchat-root] a.button:global(.phone-number-or-url-anchor):focus-visible {
314
- outline: 2px solid var(--cc-primary-color-focus);
314
+ outline: 2px solid var(--cc-primary-color-focus, var(--cc-primary-color));
315
315
  outline-offset: 2px;
316
- box-shadow: 0 0 0 4px var(--cc-primary-contrast-color);
316
+ box-shadow: 0 0 0 4px var(--cc-primary-contrast-color, #ffffff);
317
+ }
318
+
319
+ /* Fallback for consumers without [data-cognigy-webchat-root] wrapper.
320
+ :where() has zero specificity, so the rules above win when the wrapper exists. */
321
+ :where(button.button):focus-visible,
322
+ :where(a.button):focus-visible {
323
+ outline: 2px solid var(--cc-primary-color-focus, var(--cc-primary-color));
324
+ outline-offset: 2px;
325
+ box-shadow: 0 0 0 4px var(--cc-primary-contrast-color, #ffffff);
317
326
  }
318
327
 
319
328
  button.button:disabled,
@@ -322,7 +331,7 @@ button.button:disabled:focus,
322
331
  a.button.disabled,
323
332
  a.button.disabled:hover,
324
333
  a.button.disabled:focus {
325
- background: var(--cc-primary-color-disabled);
334
+ background: var(--cc-primary-color-disabled, color-mix(in srgb, var(--cc-primary-color) 40%, #999));
326
335
  cursor: default;
327
336
  pointer-events: none;
328
337
  }
@@ -349,6 +358,6 @@ a.button.disabled:focus {
349
358
  left: 0;
350
359
  width: 40px;
351
360
  height: 100%;
352
- border-right: 2px solid var(--cc-primary-contrast-color);
361
+ border-right: 2px solid var(--cc-primary-contrast-color, #ffffff);
353
362
  }
354
363
  </style>
@@ -64,7 +64,6 @@ const bubbleStyle = computed((): CSSProperties => {
64
64
 
65
65
  <style module>
66
66
  .bubble {
67
- border-radius: 15px;
68
67
  border: 1px solid var(--cc-border-bot-message, transparent);
69
68
  padding: 12px;
70
69
  max-width: 295px;
@@ -73,6 +72,9 @@ const bubbleStyle = computed((): CSSProperties => {
73
72
  margin-block-end: 12px;
74
73
  word-break: break-word;
75
74
  white-space: pre-wrap;
75
+ box-shadow: var(--cc-message-shadow, rgba(151, 124, 156, 0.1) 0px 5px 9px 0px,
76
+ rgba(203, 195, 212, 0.1) 0px 5px 16px 0px,
77
+ rgba(216, 212, 221, 0.1) 0px 8px 20px 0px);
76
78
  }
77
79
 
78
80
  .bubble.disableBorder {
@@ -97,13 +99,13 @@ article:global(.user) .bubble {
97
99
  color: var(--cc-user-message-contrast-color, #ffffff);
98
100
  }
99
101
 
100
- article:not(:global(.user)) .incoming,
101
- article:global(.user) .incoming {
102
- margin-inline-start: none;
102
+ .incoming {
103
+ margin-inline-start: 0;
104
+ border-radius: 15px 15px 15px 0;
103
105
  }
104
106
 
105
- article:not(:global(.user)) .outgoing,
106
- article:global(.user) .outgoing {
107
+ .outgoing {
107
108
  margin-inline-start: auto;
109
+ border-radius: 15px 15px 0 15px;
108
110
  }
109
111
  </style>
@@ -68,9 +68,12 @@ const eventClasses = computed(() => {
68
68
 
69
69
  .eventPillTextWrapper {
70
70
  border-radius: 15px;
71
- background: var(--cc-black-80, rgba(0, 0, 0, 0.8));
72
- color: var(--cc-black-20, rgba(0, 0, 0, 0.2));
71
+ background: var(--cc-chat-event-background, rgba(0, 0, 0, 0.8));
72
+ color: var(--cc-chat-event-text-color, #ffffff);
73
73
  padding: 8px 12px;
74
+ box-shadow: var(--cc-message-shadow, rgba(151, 124, 156, 0.1) 0px 5px 9px 0px,
75
+ rgba(203, 195, 212, 0.1) 0px 5px 16px 0px,
76
+ rgba(216, 212, 221, 0.1) 0px 8px 20px 0px);
74
77
  }
75
78
 
76
79
  .eventText {
@@ -71,7 +71,7 @@ const indicatorClasses = computed(() => {
71
71
  align-items: center;
72
72
  background-color: var(--cc-white, #ffffff);
73
73
  border-radius: 15px;
74
- border: 1px solid var(--cc-black-80, rgba(0, 0, 0, 0.8));
74
+ border: 1px solid var(--cc-border-media-card, var(--cc-black-80, rgba(0, 0, 0, 0.8)));
75
75
  box-sizing: border-box;
76
76
  display: flex;
77
77
  gap: 6px;
@@ -82,6 +82,9 @@ const indicatorClasses = computed(() => {
82
82
  width: 62px;
83
83
  margin-block: var(--webchat-message-margin-block, 24px);
84
84
  margin-inline: var(--webchat-message-margin-inline, 20px);
85
+ box-shadow: var(--cc-message-shadow, rgba(151, 124, 156, 0.1) 0px 5px 9px 0px,
86
+ rgba(203, 195, 212, 0.1) 0px 5px 16px 0px,
87
+ rgba(216, 212, 221, 0.1) 0px 8px 20px 0px);
85
88
  }
86
89
 
87
90
  .typingIndicator.disableBorder {