@cognigy/chat-components-vue 0.2.0 → 0.3.1
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 +3744 -3642
- package/dist/components/Message.vue.d.ts +4 -0
- package/dist/components/common/ActionButton.vue.d.ts +2 -0
- package/dist/components/common/ActionButtons.vue.d.ts +2 -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 +53 -7
- package/src/components/common/ActionButtons.vue +4 -0
- 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 +54 -40
- package/src/components/messages/ListItem.vue +97 -63
- 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;
|
|
@@ -24,6 +24,7 @@ interface Props {
|
|
|
24
24
|
dataMessageId?: string;
|
|
25
25
|
onEmitAnalytics?: AnalyticsEventCallback;
|
|
26
26
|
size?: 'small' | 'large';
|
|
27
|
+
variant?: 'primary' | 'secondary';
|
|
27
28
|
id?: string;
|
|
28
29
|
className?: string;
|
|
29
30
|
openXAppOverlay?: (url: string | undefined) => void;
|
|
@@ -44,6 +45,7 @@ declare const __VLS_component: import('vue').DefineComponent<Props, {}, {}, {},
|
|
|
44
45
|
disabled: boolean;
|
|
45
46
|
config: ChatConfig;
|
|
46
47
|
className: string;
|
|
48
|
+
variant: "primary" | "secondary";
|
|
47
49
|
customIcon: CustomIcon;
|
|
48
50
|
showUrlIcon: boolean;
|
|
49
51
|
dataMessageId: string;
|
|
@@ -14,6 +14,7 @@ interface Props {
|
|
|
14
14
|
dataMessageId?: string;
|
|
15
15
|
onEmitAnalytics?: AnalyticsEventCallback;
|
|
16
16
|
size?: 'small' | 'large';
|
|
17
|
+
variant?: 'primary' | 'secondary';
|
|
17
18
|
templateTextId?: string;
|
|
18
19
|
openXAppOverlay?: (url: string | undefined) => void;
|
|
19
20
|
}
|
|
@@ -22,6 +23,7 @@ declare const _default: import('vue').DefineComponent<Props, {}, {}, {}, {}, imp
|
|
|
22
23
|
action: MessageSender;
|
|
23
24
|
config: ChatConfig;
|
|
24
25
|
className: string;
|
|
26
|
+
variant: "primary" | "secondary";
|
|
25
27
|
customIcon: CustomIcon;
|
|
26
28
|
showUrlIcon: boolean;
|
|
27
29
|
dataMessageId: string;
|
|
@@ -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.1",
|
|
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;
|
|
@@ -33,6 +33,7 @@ interface Props {
|
|
|
33
33
|
dataMessageId?: string
|
|
34
34
|
onEmitAnalytics?: AnalyticsEventCallback
|
|
35
35
|
size?: 'small' | 'large'
|
|
36
|
+
variant?: 'primary' | 'secondary'
|
|
36
37
|
id?: string
|
|
37
38
|
className?: string
|
|
38
39
|
openXAppOverlay?: (url: string | undefined) => void
|
|
@@ -47,6 +48,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
47
48
|
dataMessageId: undefined,
|
|
48
49
|
onEmitAnalytics: undefined,
|
|
49
50
|
size: 'small',
|
|
51
|
+
variant: 'primary',
|
|
50
52
|
id: undefined,
|
|
51
53
|
className: '',
|
|
52
54
|
openXAppOverlay: undefined,
|
|
@@ -158,6 +160,7 @@ const target = computed(() => {
|
|
|
158
160
|
// CSS classes
|
|
159
161
|
const buttonClasses = computed(() => {
|
|
160
162
|
const classes = [styles.button]
|
|
163
|
+
if (props.variant === 'secondary') classes.push(styles.secondary)
|
|
161
164
|
if (isWebURL.value) classes.push(styles.url)
|
|
162
165
|
if (props.className) classes.push(props.className)
|
|
163
166
|
if (props.disabled) {
|
|
@@ -282,7 +285,7 @@ a.button {
|
|
|
282
285
|
gap: 10px;
|
|
283
286
|
text-decoration: none;
|
|
284
287
|
background: var(--cc-primary-color);
|
|
285
|
-
color: var(--cc-primary-contrast-color);
|
|
288
|
+
color: var(--cc-primary-contrast-color, #ffffff);
|
|
286
289
|
border: none;
|
|
287
290
|
outline: none;
|
|
288
291
|
position: relative;
|
|
@@ -296,7 +299,7 @@ button.button svg,
|
|
|
296
299
|
a.button svg,
|
|
297
300
|
button.button path,
|
|
298
301
|
a.button svg path {
|
|
299
|
-
fill: var(--cc-primary-contrast-color);
|
|
302
|
+
fill: var(--cc-primary-contrast-color, #ffffff);
|
|
300
303
|
width: 12px;
|
|
301
304
|
}
|
|
302
305
|
|
|
@@ -304,16 +307,25 @@ button.button:hover,
|
|
|
304
307
|
a.button:hover,
|
|
305
308
|
button.button:focus,
|
|
306
309
|
a.button:focus {
|
|
307
|
-
background: var(--cc-primary-color-hover);
|
|
310
|
+
background: var(--cc-primary-color-hover, color-mix(in srgb, var(--cc-primary-color), black 15%));
|
|
308
311
|
}
|
|
309
312
|
|
|
310
313
|
/* Explicitly increase the specificity of the :focus-visible selector */
|
|
311
314
|
[data-cognigy-webchat-root] button.button:focus-visible,
|
|
312
315
|
[data-cognigy-webchat-root] a.button:focus-visible,
|
|
313
316
|
[data-cognigy-webchat-root] a.button:global(.phone-number-or-url-anchor):focus-visible {
|
|
314
|
-
outline: 2px solid var(--cc-primary-color-focus);
|
|
317
|
+
outline: 2px solid var(--cc-primary-color-focus, var(--cc-primary-color));
|
|
315
318
|
outline-offset: 2px;
|
|
316
|
-
box-shadow: 0 0 0 4px var(--cc-primary-contrast-color);
|
|
319
|
+
box-shadow: 0 0 0 4px var(--cc-primary-contrast-color, #ffffff);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* Fallback for consumers without [data-cognigy-webchat-root] wrapper.
|
|
323
|
+
:where() has zero specificity, so the rules above win when the wrapper exists. */
|
|
324
|
+
:where(button.button):focus-visible,
|
|
325
|
+
:where(a.button):focus-visible {
|
|
326
|
+
outline: 2px solid var(--cc-primary-color-focus, var(--cc-primary-color));
|
|
327
|
+
outline-offset: 2px;
|
|
328
|
+
box-shadow: 0 0 0 4px var(--cc-primary-contrast-color, #ffffff);
|
|
317
329
|
}
|
|
318
330
|
|
|
319
331
|
button.button:disabled,
|
|
@@ -322,11 +334,45 @@ button.button:disabled:focus,
|
|
|
322
334
|
a.button.disabled,
|
|
323
335
|
a.button.disabled:hover,
|
|
324
336
|
a.button.disabled:focus {
|
|
325
|
-
background: var(--cc-primary-color-disabled);
|
|
337
|
+
background: var(--cc-primary-color-disabled, color-mix(in srgb, var(--cc-primary-color) 40%, #999));
|
|
338
|
+
cursor: default;
|
|
339
|
+
pointer-events: none;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/* Secondary variant: outlined style */
|
|
343
|
+
button.button.secondary,
|
|
344
|
+
a.button.secondary {
|
|
345
|
+
background: transparent;
|
|
346
|
+
color: var(--cc-primary-color);
|
|
347
|
+
border: 1px solid var(--cc-primary-color);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
button.button.secondary:hover,
|
|
351
|
+
a.button.secondary:hover,
|
|
352
|
+
button.button.secondary:focus,
|
|
353
|
+
a.button.secondary:focus {
|
|
354
|
+
background: color-mix(in srgb, var(--cc-primary-color) 10%, transparent);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
button.button.secondary:disabled,
|
|
358
|
+
button.button.secondary:disabled:hover,
|
|
359
|
+
a.button.secondary.disabled,
|
|
360
|
+
a.button.secondary.disabled:hover {
|
|
361
|
+
background: transparent;
|
|
362
|
+
color: var(--cc-primary-color-disabled, color-mix(in srgb, var(--cc-primary-color) 40%, #999));
|
|
363
|
+
border-color: var(--cc-primary-color-disabled, color-mix(in srgb, var(--cc-primary-color) 40%, #999));
|
|
326
364
|
cursor: default;
|
|
327
365
|
pointer-events: none;
|
|
328
366
|
}
|
|
329
367
|
|
|
368
|
+
/* Focus-visible for secondary */
|
|
369
|
+
:where(button.button.secondary):focus-visible,
|
|
370
|
+
:where(a.button.secondary):focus-visible {
|
|
371
|
+
outline: 2px solid var(--cc-primary-color-focus, var(--cc-primary-color));
|
|
372
|
+
outline-offset: 2px;
|
|
373
|
+
box-shadow: 0 0 0 4px var(--cc-primary-contrast-color, #ffffff);
|
|
374
|
+
}
|
|
375
|
+
|
|
330
376
|
.buttonLabelWithImage {
|
|
331
377
|
margin-left: 40px;
|
|
332
378
|
}
|
|
@@ -349,6 +395,6 @@ a.button.disabled:focus {
|
|
|
349
395
|
left: 0;
|
|
350
396
|
width: 40px;
|
|
351
397
|
height: 100%;
|
|
352
|
-
border-right: 2px solid var(--cc-primary-contrast-color);
|
|
398
|
+
border-right: 2px solid var(--cc-primary-contrast-color, #ffffff);
|
|
353
399
|
}
|
|
354
400
|
</style>
|
|
@@ -18,6 +18,7 @@ interface Props {
|
|
|
18
18
|
dataMessageId?: string
|
|
19
19
|
onEmitAnalytics?: AnalyticsEventCallback
|
|
20
20
|
size?: 'small' | 'large'
|
|
21
|
+
variant?: 'primary' | 'secondary'
|
|
21
22
|
templateTextId?: string
|
|
22
23
|
openXAppOverlay?: (url: string | undefined) => void
|
|
23
24
|
}
|
|
@@ -35,6 +36,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
35
36
|
dataMessageId: undefined,
|
|
36
37
|
onEmitAnalytics: undefined,
|
|
37
38
|
size: 'small',
|
|
39
|
+
variant: 'primary',
|
|
38
40
|
templateTextId: undefined,
|
|
39
41
|
openXAppOverlay: undefined,
|
|
40
42
|
})
|
|
@@ -118,6 +120,7 @@ const containerClasses = computed(() => {
|
|
|
118
120
|
:config="config"
|
|
119
121
|
:on-emit-analytics="onEmitAnalytics"
|
|
120
122
|
:size="size"
|
|
123
|
+
:variant="variant"
|
|
121
124
|
:data-message-id="dataMessageId"
|
|
122
125
|
:class-name="buttonClassName"
|
|
123
126
|
:open-x-app-overlay="openXAppOverlay"
|
|
@@ -140,6 +143,7 @@ const containerClasses = computed(() => {
|
|
|
140
143
|
:config="config"
|
|
141
144
|
:on-emit-analytics="onEmitAnalytics"
|
|
142
145
|
:size="size"
|
|
146
|
+
:variant="variant"
|
|
143
147
|
:data-message-id="dataMessageId"
|
|
144
148
|
:class-name="buttonClassName"
|
|
145
149
|
:open-x-app-overlay="openXAppOverlay"
|
|
@@ -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>
|