@cognigy/chat-components-vue 0.1.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 +12386 -5741
- package/dist/components/Message.vue.d.ts +4 -0
- package/dist/components/common/Typography.vue.d.ts +1 -1
- package/dist/components/messages/AdaptiveCardRenderer.vue.d.ts +35 -0
- 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 +105 -1
- package/dist/utils/helpers.d.ts +3 -2
- package/dist/utils/matcher.d.ts +3 -3
- package/dist/utils/theme.d.ts +12 -1
- package/package.json +8 -3
- package/src/components/Message.vue +98 -55
- 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/AdaptiveCard.vue +322 -225
- package/src/components/messages/AdaptiveCardRenderer.vue +260 -0
- 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/useCollation.ts +28 -45
- package/src/composables/useLiveRegion.ts +101 -0
- package/src/index.ts +4 -1
- package/src/types/index.ts +127 -2
- package/src/utils/helpers.ts +46 -24
- package/src/utils/matcher.ts +20 -6
- package/src/utils/sanitize.ts +1 -2
- package/src/utils/theme.ts +42 -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;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host config input type - plain object that HostConfig constructor parses.
|
|
3
|
+
* We don't use Partial<HostConfig> because HostConfig properties are class instances,
|
|
4
|
+
* but the constructor accepts plain objects and converts them internally.
|
|
5
|
+
*/
|
|
6
|
+
type HostConfigInput = Record<string, unknown>;
|
|
7
|
+
interface Props {
|
|
8
|
+
/**
|
|
9
|
+
* The Adaptive Card payload to render
|
|
10
|
+
*/
|
|
11
|
+
payload?: Record<string, unknown>;
|
|
12
|
+
/**
|
|
13
|
+
* Host configuration for styling the card.
|
|
14
|
+
* Accepts a plain object matching the HostConfig structure - the library
|
|
15
|
+
* parses this internally when constructing the HostConfig instance.
|
|
16
|
+
*/
|
|
17
|
+
hostConfig?: HostConfigInput;
|
|
18
|
+
/**
|
|
19
|
+
* When true, disables all inputs and buttons after rendering
|
|
20
|
+
* Useful for displaying submitted cards in chat history
|
|
21
|
+
*/
|
|
22
|
+
readonly?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Data to pre-fill into input fields
|
|
25
|
+
* Keys should match input element IDs in the card
|
|
26
|
+
* Used to show submitted values in chat history
|
|
27
|
+
*/
|
|
28
|
+
inputData?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
declare const _default: import('vue').DefineComponent<Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<Props> & Readonly<{}>, {
|
|
31
|
+
readonly: boolean;
|
|
32
|
+
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
|
|
33
|
+
targetRef: HTMLDivElement;
|
|
34
|
+
}, HTMLDivElement>;
|
|
35
|
+
export default _default;
|
|
@@ -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,7 +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;
|
|
109
|
+
adaptiveCardTextColor?: string;
|
|
110
|
+
adaptiveCardInputColor?: string;
|
|
111
|
+
adaptiveCardInputBackground?: string;
|
|
112
|
+
adaptiveCardInputBorder?: string;
|
|
113
|
+
filePreviewBackground?: string;
|
|
114
|
+
filePreviewTextColor?: string;
|
|
115
|
+
filePreviewSecondaryTextColor?: string;
|
|
116
|
+
chatEventBackground?: string;
|
|
117
|
+
chatEventTextColor?: string;
|
|
118
|
+
messageShadow?: string;
|
|
82
119
|
};
|
|
83
120
|
behavior?: {
|
|
84
121
|
renderMarkdown?: boolean;
|
|
@@ -86,6 +123,21 @@ export interface ChatSettings {
|
|
|
86
123
|
messageDelay?: number;
|
|
87
124
|
collateStreamedOutputs?: boolean;
|
|
88
125
|
focusInputAfterPostback?: boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Controls adaptive card interactivity.
|
|
128
|
+
* - `true`: All cards are readonly (presentation only), regardless of submitted data
|
|
129
|
+
* - `false` or `undefined`: Smart default - readonly only if card has submitted data
|
|
130
|
+
*
|
|
131
|
+
* Use `true` for chat history/transcript displays where no interaction is needed.
|
|
132
|
+
* Use `false`/omit for interactive chat interfaces with smart auto-detection.
|
|
133
|
+
*/
|
|
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;
|
|
89
141
|
};
|
|
90
142
|
widgetSettings?: {
|
|
91
143
|
enableDefaultPreview?: boolean;
|
|
@@ -174,6 +226,14 @@ export interface MessageProps {
|
|
|
174
226
|
disableHeader?: boolean;
|
|
175
227
|
plugins?: MessagePlugin[];
|
|
176
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;
|
|
177
237
|
}
|
|
178
238
|
/**
|
|
179
239
|
* Options for match rules and plugins
|
|
@@ -230,6 +290,14 @@ export interface MessageContext {
|
|
|
230
290
|
theme?: ChatTheme;
|
|
231
291
|
action?: MessageSender;
|
|
232
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;
|
|
233
301
|
}
|
|
234
302
|
/**
|
|
235
303
|
* DatePicker plugin data interface
|
|
@@ -271,5 +339,41 @@ export interface IWebchatChannelPayload {
|
|
|
271
339
|
quick_replies?: QuickReply[];
|
|
272
340
|
attachment?: TemplateAttachment | AudioAttachment | ImageAttachment | VideoAttachment;
|
|
273
341
|
};
|
|
274
|
-
adaptiveCard?: unknown
|
|
342
|
+
adaptiveCard?: Record<string, unknown>;
|
|
343
|
+
/** Submitted adaptive card data */
|
|
344
|
+
adaptiveCardData?: Record<string, unknown>;
|
|
345
|
+
data?: Record<string, unknown>;
|
|
346
|
+
formData?: Record<string, unknown>;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Cognigy message data structure
|
|
350
|
+
* Shape of message.data._cognigy
|
|
351
|
+
*/
|
|
352
|
+
export interface ICognigyData {
|
|
353
|
+
_webchat?: IWebchatChannelPayload;
|
|
354
|
+
_defaultPreview?: IWebchatChannelPayload;
|
|
355
|
+
_facebook?: IWebchatChannelPayload;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Plugin payload structure for custom message types
|
|
359
|
+
*/
|
|
360
|
+
export interface IPluginPayload {
|
|
361
|
+
type?: string;
|
|
362
|
+
payload?: Record<string, unknown>;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Extended message data with Cognigy-specific fields
|
|
366
|
+
* Use this when accessing message.data with Cognigy payload structures
|
|
367
|
+
*/
|
|
368
|
+
export interface IMessageDataExtended {
|
|
369
|
+
_cognigy?: ICognigyData;
|
|
370
|
+
_plugin?: IPluginPayload;
|
|
371
|
+
/** Adaptive card submission request data */
|
|
372
|
+
request?: {
|
|
373
|
+
value?: Record<string, unknown>;
|
|
374
|
+
};
|
|
375
|
+
/** Direct adaptive card data fields (fallback locations) */
|
|
376
|
+
adaptiveCardData?: Record<string, unknown>;
|
|
377
|
+
data?: Record<string, unknown>;
|
|
378
|
+
formData?: Record<string, unknown>;
|
|
275
379
|
}
|
package/dist/utils/helpers.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export declare function moveFocusToMessageFocusTarget(dataMessageId: string): vo
|
|
|
24
24
|
* - Works with URLs starting with http/https, www., or just domain/subdomain
|
|
25
25
|
* - Will only match URLs at the beginning or following whitespace
|
|
26
26
|
* - Will not work with emails
|
|
27
|
+
* - URLs are escaped to prevent injection attacks
|
|
27
28
|
*/
|
|
28
29
|
export declare function replaceUrlsWithHTMLanchorElem(text: string): string;
|
|
29
30
|
/**
|
|
@@ -47,9 +48,9 @@ export declare function getFileExtension(fileNameWithExtension: string): string
|
|
|
47
48
|
*/
|
|
48
49
|
export declare function getSizeLabel(size: number): string;
|
|
49
50
|
/**
|
|
50
|
-
* Valid image MIME types for file attachments
|
|
51
|
+
* Valid image MIME types for file attachments (Set for O(1) lookup)
|
|
51
52
|
*/
|
|
52
|
-
export declare const VALID_IMAGE_MIME_TYPES: string
|
|
53
|
+
export declare const VALID_IMAGE_MIME_TYPES: Set<string>;
|
|
53
54
|
/**
|
|
54
55
|
* Checks if attachment is a valid image type
|
|
55
56
|
*/
|
package/dist/utils/matcher.d.ts
CHANGED
|
@@ -5,9 +5,9 @@ import { ChatConfig, MatchRule, MessagePlugin, MatchResult } from '../types';
|
|
|
5
5
|
*/
|
|
6
6
|
export declare function getChannelPayload(message: IMessage, config?: ChatConfig): any;
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Returns a copy of the default match rules.
|
|
9
|
+
* Exposed for testing and extension purposes.
|
|
10
|
+
* Returns a new array to prevent mutation of internal rules.
|
|
11
11
|
*/
|
|
12
12
|
export declare function createDefaultMatchRules(): MatchRule[];
|
|
13
13
|
/**
|
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"
|
|
@@ -63,13 +66,15 @@
|
|
|
63
66
|
"@braintree/sanitize-url": "^7.1.1",
|
|
64
67
|
"@cognigy/socket-client": "5.0.0-beta.25",
|
|
65
68
|
"@fontsource/figtree": "5.2.10",
|
|
69
|
+
"adaptivecards": "^3.0.5",
|
|
66
70
|
"dompurify": "^3.0.11",
|
|
67
71
|
"flatpickr": "^4.6.13",
|
|
68
72
|
"markdown-it": "^14.1.0",
|
|
69
73
|
"moment": "^2.30.1",
|
|
70
|
-
"swiper": "^
|
|
74
|
+
"swiper": "^11.2.10"
|
|
71
75
|
},
|
|
72
76
|
"devDependencies": {
|
|
77
|
+
"@playwright/test": "^1.58.2",
|
|
73
78
|
"@types/markdown-it": "^14.1.2",
|
|
74
79
|
"@types/node": "^25.0.9",
|
|
75
80
|
"@vitejs/plugin-vue": "^6.0.0",
|
|
@@ -78,7 +83,7 @@
|
|
|
78
83
|
"eslint": "^9.0.0",
|
|
79
84
|
"eslint-plugin-vue": "^10.7.0",
|
|
80
85
|
"globals": "^17.0.0",
|
|
81
|
-
"jsdom": "^
|
|
86
|
+
"jsdom": "^28.0.0",
|
|
82
87
|
"npm-check-updates": "^19.3.1",
|
|
83
88
|
"typescript": "^5.9.0",
|
|
84
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,70 +57,108 @@ 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
|
|
71
|
+
const messageContext = reactive<MessageContext>({
|
|
61
72
|
message: props.message,
|
|
62
|
-
config: props.config
|
|
63
|
-
action: props.action
|
|
64
|
-
onEmitAnalytics: props.onEmitAnalytics
|
|
65
|
-
|
|
73
|
+
config: props.config,
|
|
74
|
+
action: props.action,
|
|
75
|
+
onEmitAnalytics: props.onEmitAnalytics,
|
|
76
|
+
messageParams: props.messageParams,
|
|
77
|
+
openXAppOverlay: props.openXAppOverlay,
|
|
78
|
+
onSlideChange: props.onSlideChange,
|
|
79
|
+
onImageClick: props.onImageClick,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Keep context in sync with props changes
|
|
83
|
+
watchEffect(() => {
|
|
84
|
+
messageContext.message = props.message
|
|
85
|
+
messageContext.config = props.config
|
|
86
|
+
messageContext.action = props.action
|
|
87
|
+
messageContext.onEmitAnalytics = props.onEmitAnalytics
|
|
88
|
+
messageContext.messageParams = props.messageParams
|
|
89
|
+
messageContext.openXAppOverlay = props.openXAppOverlay
|
|
90
|
+
messageContext.onSlideChange = props.onSlideChange
|
|
91
|
+
messageContext.onImageClick = props.onImageClick
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
provideMessageContext(messageContext);
|
|
66
95
|
|
|
67
96
|
// Component map for internal match rules (maps rule names to Vue components)
|
|
68
97
|
const componentMap: Record<string, Component> = {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
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
|
+
};
|
|
80
109
|
|
|
81
110
|
// Match message to appropriate components
|
|
82
111
|
const matchedComponents = computed(() => {
|
|
83
|
-
const matched = match(props.message, props.config, props.plugins)
|
|
112
|
+
const matched = match(props.message, props.config, props.plugins);
|
|
84
113
|
|
|
85
114
|
if (!Array.isArray(matched) || matched.length < 1) {
|
|
86
|
-
return []
|
|
115
|
+
return [];
|
|
87
116
|
}
|
|
88
117
|
|
|
89
118
|
// Resolve components from match results
|
|
90
119
|
return matched
|
|
91
|
-
.map(rule => {
|
|
120
|
+
.map((rule) => {
|
|
92
121
|
// External plugins (MessagePlugin) provide their own component
|
|
93
122
|
if (isMessagePlugin(rule)) {
|
|
94
|
-
return rule.component
|
|
123
|
+
return rule.component;
|
|
95
124
|
}
|
|
96
125
|
// Internal rules (MatchRule) use name lookup in componentMap
|
|
97
|
-
return componentMap[rule.name] ?? null
|
|
126
|
+
return componentMap[rule.name] ?? null;
|
|
98
127
|
})
|
|
99
|
-
.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'
|
|
100
140
|
})
|
|
101
141
|
|
|
102
142
|
// Root element classes
|
|
103
143
|
const rootClasses = computed(() => {
|
|
104
144
|
return [
|
|
105
145
|
'webchat-message-row',
|
|
106
|
-
props.message.source,
|
|
146
|
+
props.message.source, // keep global source class (for ChatBubble color selectors)
|
|
107
147
|
$style.message,
|
|
108
|
-
props.message.source ===
|
|
109
|
-
props.message.source ===
|
|
110
|
-
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
|
|
111
152
|
].filter(Boolean)
|
|
112
153
|
})
|
|
113
154
|
|
|
114
|
-
// CSS variable injection from config colors
|
|
155
|
+
// CSS variable injection from config colors and theme font
|
|
115
156
|
const cssVariableStyle = computed(() => {
|
|
116
|
-
return
|
|
117
|
-
|
|
157
|
+
return {
|
|
158
|
+
...configColorsToCssVariables(props.config?.settings?.colors),
|
|
159
|
+
...themeFontToCssVariable(props.theme),
|
|
160
|
+
}
|
|
161
|
+
});
|
|
118
162
|
</script>
|
|
119
163
|
|
|
120
164
|
<style module>
|
|
@@ -123,20 +167,19 @@ const cssVariableStyle = computed(() => {
|
|
|
123
167
|
flex-direction: column;
|
|
124
168
|
gap: 8px;
|
|
125
169
|
margin-bottom: 16px;
|
|
170
|
+
font-family: var(--cc-font-family, 'Figtree', sans-serif);
|
|
171
|
+
padding-left: 56px;
|
|
172
|
+
padding-right: 16px;
|
|
126
173
|
}
|
|
127
174
|
|
|
128
|
-
.message.
|
|
175
|
+
.message.incoming {
|
|
129
176
|
align-items: flex-start;
|
|
130
177
|
}
|
|
131
178
|
|
|
132
|
-
.message.
|
|
179
|
+
.message.outgoing {
|
|
133
180
|
align-items: flex-end;
|
|
134
181
|
}
|
|
135
182
|
|
|
136
|
-
.message.agent {
|
|
137
|
-
align-items: flex-start;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
183
|
/* Screen reader only */
|
|
141
184
|
.srOnly {
|
|
142
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>
|