@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
|
@@ -64,23 +64,23 @@
|
|
|
64
64
|
</video>
|
|
65
65
|
</div>
|
|
66
66
|
|
|
67
|
-
<!-- Download transcript button -->
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
67
|
+
<!-- Download transcript button (split-border: flush card extension below player) -->
|
|
68
|
+
<button
|
|
69
|
+
v-if="videoData.altText"
|
|
70
|
+
:class="[$style.downloadButton, 'webchat-buttons-template-button-video']"
|
|
71
|
+
@click="downloadTranscript"
|
|
72
|
+
>
|
|
73
|
+
<DownloadIcon :class="$style.downloadIcon" />
|
|
74
|
+
Download Transcript
|
|
75
|
+
</button>
|
|
76
|
+
<a
|
|
77
|
+
v-if="videoData.altText"
|
|
78
|
+
ref="downloadLinkRef"
|
|
79
|
+
:href="transcriptDataUrl"
|
|
80
|
+
download="video-transcript.txt"
|
|
81
|
+
style="display: none"
|
|
82
|
+
aria-hidden="true"
|
|
83
|
+
/>
|
|
84
84
|
</div>
|
|
85
85
|
</template>
|
|
86
86
|
|
|
@@ -230,6 +230,10 @@ onMounted(() => {
|
|
|
230
230
|
border-radius: var(--cc-bubble-border-radius, 15px);
|
|
231
231
|
max-width: 295px;
|
|
232
232
|
position: relative;
|
|
233
|
+
background-color: var(--cc-white, #ffffff);
|
|
234
|
+
box-shadow: var(--cc-message-shadow, rgba(151, 124, 156, 0.1) 0px 5px 9px 0px,
|
|
235
|
+
rgba(203, 195, 212, 0.1) 0px 5px 16px 0px,
|
|
236
|
+
rgba(216, 212, 221, 0.1) 0px 8px 20px 0px);
|
|
233
237
|
}
|
|
234
238
|
|
|
235
239
|
.player {
|
|
@@ -250,8 +254,13 @@ onMounted(() => {
|
|
|
250
254
|
overflow: hidden;
|
|
251
255
|
}
|
|
252
256
|
|
|
253
|
-
|
|
254
|
-
|
|
257
|
+
/* Split-border strategy: playerWrapper gets border with no bottom,
|
|
258
|
+
downloadButton gets border with no top — seamless card appearance */
|
|
259
|
+
.wrapperWithButton .playerWrapper {
|
|
260
|
+
border: 1px solid var(--cc-border-media-card, var(--cc-black-80, rgba(0, 0, 0, 0.8)));
|
|
261
|
+
border-bottom: none;
|
|
262
|
+
border-radius: var(--cc-bubble-border-radius, 15px) var(--cc-bubble-border-radius, 15px) 0 0;
|
|
263
|
+
overflow: hidden;
|
|
255
264
|
}
|
|
256
265
|
|
|
257
266
|
.wrapperWithButton .player {
|
|
@@ -259,11 +268,9 @@ onMounted(() => {
|
|
|
259
268
|
border-bottom-right-radius: 0px;
|
|
260
269
|
}
|
|
261
270
|
|
|
262
|
-
.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
border-bottom-left-radius: var(--cc-bubble-border-radius, 15px);
|
|
266
|
-
border-bottom-right-radius: var(--cc-bubble-border-radius, 15px);
|
|
271
|
+
.wrapperWithButton .lightOverlay {
|
|
272
|
+
border-bottom-left-radius: 0px;
|
|
273
|
+
border-bottom-right-radius: 0px;
|
|
267
274
|
}
|
|
268
275
|
|
|
269
276
|
.downloadButton {
|
|
@@ -272,11 +279,13 @@ onMounted(() => {
|
|
|
272
279
|
justify-content: center;
|
|
273
280
|
gap: 10px;
|
|
274
281
|
width: 100%;
|
|
282
|
+
box-sizing: border-box;
|
|
275
283
|
padding: 12px;
|
|
276
284
|
background-color: var(--cc-primary-color, #1976d2);
|
|
277
285
|
color: var(--cc-primary-contrast-color, #ffffff);
|
|
278
|
-
border:
|
|
279
|
-
border-
|
|
286
|
+
border: 1px solid var(--cc-border-media-card, var(--cc-black-80, rgba(0, 0, 0, 0.8)));
|
|
287
|
+
border-top: none;
|
|
288
|
+
border-radius: 0 0 var(--cc-bubble-border-radius, 15px) var(--cc-bubble-border-radius, 15px);
|
|
280
289
|
cursor: pointer;
|
|
281
290
|
font-size: 14px;
|
|
282
291
|
font-weight: 600;
|
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
|
|
12
|
+
import { onMounted, onUnmounted } from 'vue'
|
|
13
|
+
|
|
14
|
+
const LIVE_REGION_ID = 'webchat-live-region'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get or create the shared live region element in the DOM
|
|
18
|
+
*/
|
|
19
|
+
function getOrCreateLiveRegion(): HTMLDivElement {
|
|
20
|
+
let region = document.getElementById(LIVE_REGION_ID) as HTMLDivElement | null
|
|
21
|
+
|
|
22
|
+
if (!region) {
|
|
23
|
+
region = document.createElement('div')
|
|
24
|
+
region.id = LIVE_REGION_ID
|
|
25
|
+
region.setAttribute('role', 'status')
|
|
26
|
+
region.setAttribute('aria-live', 'polite')
|
|
27
|
+
region.setAttribute('aria-atomic', 'true')
|
|
28
|
+
|
|
29
|
+
// Visually hidden but accessible to screen readers
|
|
30
|
+
Object.assign(region.style, {
|
|
31
|
+
position: 'absolute',
|
|
32
|
+
width: '1px',
|
|
33
|
+
height: '1px',
|
|
34
|
+
padding: '0',
|
|
35
|
+
margin: '-1px',
|
|
36
|
+
overflow: 'hidden',
|
|
37
|
+
clip: 'rect(0, 0, 0, 0)',
|
|
38
|
+
whiteSpace: 'nowrap',
|
|
39
|
+
borderWidth: '0',
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
document.body.appendChild(region)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return region
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Track reference count so we only clean up when no components are using the region
|
|
50
|
+
*/
|
|
51
|
+
let refCount = 0
|
|
52
|
+
|
|
53
|
+
interface UseLiveRegionOptions {
|
|
54
|
+
/** Text to announce to screen readers */
|
|
55
|
+
announcement: string
|
|
56
|
+
/** Whether the announcement should be made (defaults to true) */
|
|
57
|
+
enabled?: boolean
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Composable that announces content to screen readers via an ARIA live region.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* // In a message component:
|
|
66
|
+
* useLiveRegion({
|
|
67
|
+
* announcement: `New message: ${messageText}`,
|
|
68
|
+
* enabled: !props.ignoreLiveRegion,
|
|
69
|
+
* })
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export function useLiveRegion(options: UseLiveRegionOptions): void {
|
|
73
|
+
onMounted(() => {
|
|
74
|
+
refCount++
|
|
75
|
+
|
|
76
|
+
if (options.enabled === false) return
|
|
77
|
+
if (!options.announcement) return
|
|
78
|
+
|
|
79
|
+
const region = getOrCreateLiveRegion()
|
|
80
|
+
|
|
81
|
+
// Clear then set to trigger screen reader re-announcement
|
|
82
|
+
region.textContent = ''
|
|
83
|
+
// Use requestAnimationFrame to ensure the empty text is processed first
|
|
84
|
+
requestAnimationFrame(() => {
|
|
85
|
+
region.textContent = options.announcement
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
onUnmounted(() => {
|
|
90
|
+
refCount--
|
|
91
|
+
|
|
92
|
+
// Clean up the live region when no components are using it
|
|
93
|
+
if (refCount <= 0) {
|
|
94
|
+
refCount = 0
|
|
95
|
+
const region = document.getElementById(LIVE_REGION_ID)
|
|
96
|
+
if (region) {
|
|
97
|
+
region.textContent = ''
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -35,6 +35,7 @@ export { useSanitize } from './composables/useSanitize'
|
|
|
35
35
|
export { useImageContext, provideImageContext, ImageContextKey } from './composables/useImageContext'
|
|
36
36
|
export { useChannelPayload, type UseChannelPayloadReturn } from './composables/useChannelPayload'
|
|
37
37
|
export { useCollation, type UseCollationReturn, type CollatedMessage } from './composables/useCollation'
|
|
38
|
+
export { useLiveRegion } from './composables/useLiveRegion'
|
|
38
39
|
|
|
39
40
|
// SVG Icons
|
|
40
41
|
export { DownloadIcon, CloseIcon, VideoPlayIcon, AudioPlayIcon, AudioPauseIcon, ArrowBackIcon, LinkIcon } from './assets/svg'
|
|
@@ -42,7 +43,7 @@ export { DownloadIcon, CloseIcon, VideoPlayIcon, AudioPlayIcon, AudioPauseIcon,
|
|
|
42
43
|
// Utilities
|
|
43
44
|
export { match, getChannelPayload } from './utils/matcher'
|
|
44
45
|
export { sanitizeHTMLWithConfig, sanitizeContent } from './utils/sanitize'
|
|
45
|
-
export { configColorsToCssVariables } from './utils/theme'
|
|
46
|
+
export { configColorsToCssVariables, themeFontToCssVariable } from './utils/theme'
|
|
46
47
|
export { getWebchatButtonLabel, interpolateString, getRandomId, moveFocusToMessageFocusTarget, replaceUrlsWithHTMLanchorElem, getBackgroundImage, getFileName, getFileExtension, getSizeLabel, isImageAttachment, VALID_IMAGE_MIME_TYPES } from './utils/helpers'
|
|
47
48
|
|
|
48
49
|
// Types
|
|
@@ -55,7 +56,9 @@ export type {
|
|
|
55
56
|
MessagePlugin,
|
|
56
57
|
MessagePluginOptions,
|
|
57
58
|
MessageContext,
|
|
59
|
+
MessageParams,
|
|
58
60
|
IMessage,
|
|
61
|
+
IStreamingMessage,
|
|
59
62
|
IWebchatButton,
|
|
60
63
|
IWebchatQuickReply,
|
|
61
64
|
IWebchatTemplateAttachment,
|
package/src/types/index.ts
CHANGED
|
@@ -6,6 +6,30 @@
|
|
|
6
6
|
import type { IMessage } from '@cognigy/socket-client'
|
|
7
7
|
import type { Component } from 'vue'
|
|
8
8
|
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Message Parameter Types
|
|
11
|
+
// =============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parameters about the current message state in the conversation.
|
|
15
|
+
* Used to control interactive element behavior (e.g., disabling quick replies after user reply).
|
|
16
|
+
*/
|
|
17
|
+
export interface MessageParams {
|
|
18
|
+
/** Whether the user has already replied to this message (disables quick reply buttons) */
|
|
19
|
+
hasReply?: boolean
|
|
20
|
+
/** Whether the conversation has ended (disables all interactive elements) */
|
|
21
|
+
isConversationEnded?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Extended IMessage with streaming animation state.
|
|
26
|
+
* Used with progressive message rendering to conditionally show/hide elements
|
|
27
|
+
* based on whether the message is still being streamed.
|
|
28
|
+
*/
|
|
29
|
+
export interface IStreamingMessage extends IMessage {
|
|
30
|
+
animationState?: 'start' | 'animating' | 'done'
|
|
31
|
+
}
|
|
32
|
+
|
|
9
33
|
// =============================================================================
|
|
10
34
|
// Extended Socket Client Types
|
|
11
35
|
// =============================================================================
|
|
@@ -95,6 +119,13 @@ export interface ChatSettings {
|
|
|
95
119
|
disableUrlButtonSanitization?: boolean
|
|
96
120
|
dynamicImageAspectRatio?: boolean
|
|
97
121
|
showEngagementInChat?: boolean
|
|
122
|
+
/**
|
|
123
|
+
* Gallery layout variant:
|
|
124
|
+
* - 'default': Standard gallery with 206px wide slides
|
|
125
|
+
* - 'compact': Narrower slides with smaller navigation buttons
|
|
126
|
+
* - 'copilot': Adaptive sizing based on card count (1-card, 2-card, 3+ layouts)
|
|
127
|
+
*/
|
|
128
|
+
galleryVariant?: 'default' | 'compact' | 'copilot'
|
|
98
129
|
}
|
|
99
130
|
colors?: {
|
|
100
131
|
// Primary action colors
|
|
@@ -118,6 +149,12 @@ export interface ChatSettings {
|
|
|
118
149
|
borderUserMessage?: string
|
|
119
150
|
borderAgentMessage?: string
|
|
120
151
|
|
|
152
|
+
// Media card borders (Image, Video, Audio, Gallery, List, TypingIndicator)
|
|
153
|
+
borderMediaCard?: string
|
|
154
|
+
|
|
155
|
+
// List divider color (separator between list items)
|
|
156
|
+
listDividerColor?: string
|
|
157
|
+
|
|
121
158
|
// Link color
|
|
122
159
|
textLinkColor?: string
|
|
123
160
|
|
|
@@ -126,6 +163,18 @@ export interface ChatSettings {
|
|
|
126
163
|
adaptiveCardInputColor?: string
|
|
127
164
|
adaptiveCardInputBackground?: string
|
|
128
165
|
adaptiveCardInputBorder?: string
|
|
166
|
+
|
|
167
|
+
// File preview colors (non-image file attachment pills)
|
|
168
|
+
filePreviewBackground?: string
|
|
169
|
+
filePreviewTextColor?: string
|
|
170
|
+
filePreviewSecondaryTextColor?: string
|
|
171
|
+
|
|
172
|
+
// Chat event colors (event pill notifications)
|
|
173
|
+
chatEventBackground?: string
|
|
174
|
+
chatEventTextColor?: string
|
|
175
|
+
|
|
176
|
+
// Message shadow (applied to all message bubbles/cards)
|
|
177
|
+
messageShadow?: string
|
|
129
178
|
}
|
|
130
179
|
behavior?: {
|
|
131
180
|
renderMarkdown?: boolean
|
|
@@ -142,6 +191,12 @@ export interface ChatSettings {
|
|
|
142
191
|
* Use `false`/omit for interactive chat interfaces with smart auto-detection.
|
|
143
192
|
*/
|
|
144
193
|
adaptiveCardsReadonly?: boolean
|
|
194
|
+
/**
|
|
195
|
+
* When true, buttons/quick replies are hidden while a message is still being streamed.
|
|
196
|
+
* Works with IStreamingMessage.animationState to delay showing interactive elements
|
|
197
|
+
* until the text animation is complete.
|
|
198
|
+
*/
|
|
199
|
+
progressiveMessageRendering?: boolean
|
|
145
200
|
}
|
|
146
201
|
widgetSettings?: {
|
|
147
202
|
enableDefaultPreview?: boolean
|
|
@@ -247,6 +302,14 @@ export interface MessageProps {
|
|
|
247
302
|
disableHeader?: boolean
|
|
248
303
|
plugins?: MessagePlugin[]
|
|
249
304
|
onEmitAnalytics?: (event: string, payload?: unknown) => void
|
|
305
|
+
/** Conversation state parameters (hasReply, isConversationEnded) */
|
|
306
|
+
messageParams?: MessageParams
|
|
307
|
+
/** Callback to open an xApp overlay (for openXApp button type) */
|
|
308
|
+
openXAppOverlay?: (url: string | undefined) => void
|
|
309
|
+
/** Callback when gallery slide changes — receives (activeIndex, totalSlides) */
|
|
310
|
+
onSlideChange?: (index: number, total: number) => void
|
|
311
|
+
/** Callback when an image is clicked — receives the image URL */
|
|
312
|
+
onImageClick?: (url: string) => void
|
|
250
313
|
}
|
|
251
314
|
|
|
252
315
|
/**
|
|
@@ -318,6 +381,14 @@ export interface MessageContext {
|
|
|
318
381
|
theme?: ChatTheme
|
|
319
382
|
action?: MessageSender
|
|
320
383
|
onEmitAnalytics?: (event: string, payload?: unknown) => void
|
|
384
|
+
/** Conversation state parameters (hasReply, isConversationEnded) */
|
|
385
|
+
messageParams?: MessageParams
|
|
386
|
+
/** Callback to open an xApp overlay (for openXApp button type) */
|
|
387
|
+
openXAppOverlay?: (url: string | undefined) => void
|
|
388
|
+
/** Callback when gallery slide changes — receives (activeIndex, totalSlides) */
|
|
389
|
+
onSlideChange?: (index: number, total: number) => void
|
|
390
|
+
/** Callback when an image is clicked — receives the image URL */
|
|
391
|
+
onImageClick?: (url: string) => void
|
|
321
392
|
}
|
|
322
393
|
|
|
323
394
|
/**
|
package/src/utils/theme.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Theme utilities for config-driven CSS variable injection
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { ChatSettings } from '../types'
|
|
5
|
+
import type { ChatSettings, ChatTheme } from '../types'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Maps config.settings.colors to CSS custom properties.
|
|
@@ -47,6 +47,12 @@ export function configColorsToCssVariables(
|
|
|
47
47
|
'--cc-border-user-message': colors.borderUserMessage,
|
|
48
48
|
'--cc-border-agent-message': colors.borderAgentMessage,
|
|
49
49
|
|
|
50
|
+
// Media card borders
|
|
51
|
+
'--cc-border-media-card': colors.borderMediaCard,
|
|
52
|
+
|
|
53
|
+
// List divider color
|
|
54
|
+
'--cc-list-divider-color': colors.listDividerColor,
|
|
55
|
+
|
|
50
56
|
// Link color
|
|
51
57
|
'--cc-text-link-color': colors.textLinkColor,
|
|
52
58
|
|
|
@@ -55,6 +61,18 @@ export function configColorsToCssVariables(
|
|
|
55
61
|
'--cc-adaptive-card-input-color': colors.adaptiveCardInputColor,
|
|
56
62
|
'--cc-adaptive-card-input-background': colors.adaptiveCardInputBackground,
|
|
57
63
|
'--cc-adaptive-card-input-border': colors.adaptiveCardInputBorder,
|
|
64
|
+
|
|
65
|
+
// File preview colors
|
|
66
|
+
'--cc-file-preview-background': colors.filePreviewBackground,
|
|
67
|
+
'--cc-file-preview-text-color': colors.filePreviewTextColor,
|
|
68
|
+
'--cc-file-preview-secondary-text-color': colors.filePreviewSecondaryTextColor,
|
|
69
|
+
|
|
70
|
+
// Chat event colors
|
|
71
|
+
'--cc-chat-event-background': colors.chatEventBackground,
|
|
72
|
+
'--cc-chat-event-text-color': colors.chatEventTextColor,
|
|
73
|
+
|
|
74
|
+
// Message shadow
|
|
75
|
+
'--cc-message-shadow': colors.messageShadow,
|
|
58
76
|
}
|
|
59
77
|
|
|
60
78
|
// Filter out undefined values and return only defined CSS variables
|
|
@@ -62,3 +80,20 @@ export function configColorsToCssVariables(
|
|
|
62
80
|
Object.entries(mapping).filter(([, value]) => value !== undefined)
|
|
63
81
|
) as Record<string, string>
|
|
64
82
|
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Maps a ChatTheme's fontFamily to the --cc-font-family CSS variable.
|
|
86
|
+
* Consumers can set this to override the default Figtree font.
|
|
87
|
+
*
|
|
88
|
+
* Components should reference font-family as:
|
|
89
|
+
* font-family: var(--cc-font-family, 'Figtree', sans-serif);
|
|
90
|
+
*
|
|
91
|
+
* @param theme - The ChatTheme object
|
|
92
|
+
* @returns A record with the --cc-font-family CSS variable, or empty object
|
|
93
|
+
*/
|
|
94
|
+
export function themeFontToCssVariable(
|
|
95
|
+
theme?: ChatTheme
|
|
96
|
+
): Record<string, string> {
|
|
97
|
+
if (!theme?.fontFamily) return {}
|
|
98
|
+
return { '--cc-font-family': theme.fontFamily }
|
|
99
|
+
}
|