@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.
- package/dist/chat-components-vue.css +1 -1
- package/dist/chat-components-vue.js +3730 -3634
- package/dist/components/Message.vue.d.ts +4 -0
- package/dist/components/common/Typography.vue.d.ts +1 -1
- package/dist/components/messages/ListItem.vue.d.ts +1 -1
- package/dist/composables/useLiveRegion.d.ts +30 -0
- package/dist/index.d.ts +3 -2
- package/dist/types/index.d.ts +55 -0
- package/dist/utils/theme.d.ts +12 -1
- package/package.json +6 -2
- package/src/components/Message.vue +85 -53
- package/src/components/common/ActionButton.vue +16 -7
- package/src/components/common/ChatBubble.vue +8 -6
- package/src/components/common/ChatEvent.vue +5 -2
- package/src/components/common/TypingIndicator.vue +4 -1
- package/src/components/common/Typography.vue +56 -67
- package/src/components/messages/AudioMessage.vue +4 -1
- package/src/components/messages/DatePicker.vue +5 -27
- package/src/components/messages/FileMessage.vue +12 -3
- package/src/components/messages/Gallery.vue +96 -10
- package/src/components/messages/GalleryItem.vue +17 -5
- package/src/components/messages/ImageMessage.vue +20 -5
- package/src/components/messages/List.vue +56 -42
- package/src/components/messages/ListItem.vue +105 -68
- package/src/components/messages/TextMessage.vue +1 -1
- package/src/components/messages/TextWithButtons.vue +35 -11
- package/src/components/messages/VideoMessage.vue +35 -26
- package/src/composables/useLiveRegion.ts +101 -0
- package/src/index.ts +4 -1
- package/src/types/index.ts +71 -0
- 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 =
|
|
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?:
|
|
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';
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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
|
package/dist/utils/theme.d.ts
CHANGED
|
@@ -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.
|
|
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": "^
|
|
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 {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
36
|
-
import ImageMessage from
|
|
37
|
-
import VideoMessage from
|
|
38
|
-
import AudioMessage from
|
|
39
|
-
import TextWithButtons from
|
|
40
|
-
import Gallery from
|
|
41
|
-
import List from
|
|
42
|
-
import FileMessage from
|
|
43
|
-
import DatePicker from
|
|
44
|
-
import AdaptiveCard from
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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 ===
|
|
120
|
-
props.message.source ===
|
|
121
|
-
props.message.source ===
|
|
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
|
|
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.
|
|
175
|
+
.message.incoming {
|
|
140
176
|
align-items: flex-start;
|
|
141
177
|
}
|
|
142
178
|
|
|
143
|
-
.message.
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
.incoming {
|
|
103
|
+
margin-inline-start: 0;
|
|
104
|
+
border-radius: 15px 15px 15px 0;
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
|
|
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-
|
|
72
|
-
color: var(--cc-
|
|
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 {
|