@happyvertical/smrt-chat 0.30.0 → 0.31.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/chunks/{ChatService-Dpzc1Pa5.js → ChatService-ByEPKa30.js} +2 -2
- package/dist/chunks/{ChatService-Dpzc1Pa5.js.map → ChatService-ByEPKa30.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/agent-runtime.js +1 -1
- package/dist/manifest.json +2 -2
- package/dist/services/ChatService.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +4 -4
- package/dist/svelte/components/agent/AgentChat.svelte +6 -0
- package/dist/svelte/components/agent/ToolCallDisplay.svelte +4 -19
- package/dist/svelte/components/agent/ToolCallDisplay.svelte.d.ts.map +1 -1
- package/dist/svelte/components/agent/__tests__/ToolCallDisplay.test.js +44 -0
- package/dist/svelte/components/dialogs/RoomCreateDialog.svelte +101 -158
- package/dist/svelte/components/dialogs/RoomCreateDialog.svelte.d.ts.map +1 -1
- package/dist/svelte/components/layout/ChatLayout.svelte +11 -2
- package/dist/svelte/components/layout/ChatLayout.svelte.d.ts +2 -0
- package/dist/svelte/components/layout/ChatLayout.svelte.d.ts.map +1 -1
- package/dist/svelte/components/layout/MemberList.svelte +5 -3
- package/dist/svelte/components/layout/MemberList.svelte.d.ts.map +1 -1
- package/dist/svelte/components/layout/__tests__/ChatLayout.test.js +67 -0
- package/dist/svelte/components/messages/MessageItem.svelte +115 -1
- package/dist/svelte/components/messages/MessageItem.svelte.d.ts.map +1 -1
- package/dist/svelte/components/messages/__tests__/MessageItem.test.js +108 -0
- package/dist/svelte/components/shared/MentionAutocomplete.svelte +5 -1
- package/dist/svelte/components/shared/MentionAutocomplete.svelte.d.ts +2 -0
- package/dist/svelte/components/shared/MentionAutocomplete.svelte.d.ts.map +1 -1
- package/dist/svelte/components/shared/UserPresence.svelte +3 -3
- package/dist/svelte/components/shared/__tests__/MentionAutocomplete.test.js +66 -0
- package/dist/svelte/i18n.messages.d.ts +1 -0
- package/dist/svelte/i18n.messages.d.ts.map +1 -1
- package/dist/svelte/i18n.messages.js +1 -0
- package/package.json +8 -8
|
@@ -56,6 +56,16 @@ const formattedTime = $derived.by(() => {
|
|
|
56
56
|
});
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
+
const hasActions = $derived(
|
|
60
|
+
Boolean(onreact || onreply || (isOwn && (onedit || ondelete))),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// Stable id linking the disclosure trigger (aria-controls) to the revealed
|
|
64
|
+
// action group. The actions are a labelled group of independently
|
|
65
|
+
// keyboard-operable buttons, not an ARIA menu, so the trigger is a plain
|
|
66
|
+
// disclosure toggle (aria-expanded) rather than aria-haspopup="menu".
|
|
67
|
+
const actionsId = $derived(`message-actions-${message.id}`);
|
|
68
|
+
|
|
59
69
|
function handleReact(emoji: string) {
|
|
60
70
|
onreact?.(message.id, emoji);
|
|
61
71
|
showReactionPicker = false;
|
|
@@ -64,8 +74,40 @@ function handleReact(emoji: string) {
|
|
|
64
74
|
function toggleReactionPicker() {
|
|
65
75
|
showReactionPicker = !showReactionPicker;
|
|
66
76
|
}
|
|
77
|
+
|
|
78
|
+
// The "more actions" button is the keyboard/touch-reachable entry point that
|
|
79
|
+
// opens the action bar (a11y blocker, T2 #1391). It opens rather than toggles
|
|
80
|
+
// so it stays robust against the focus-then-click ordering a tap/keyboard
|
|
81
|
+
// activation produces; the bar is dismissed by Escape, blur, or mouse-leave.
|
|
82
|
+
function openActions() {
|
|
83
|
+
showActions = true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Collapse the action bar (and any open picker) once focus leaves the row — the
|
|
87
|
+
// keyboard/touch counterpart to onmouseleave so the actions are not hover-only.
|
|
88
|
+
function handleFocusOut(event: FocusEvent) {
|
|
89
|
+
const next = event.relatedTarget as Node | null;
|
|
90
|
+
const row = event.currentTarget as HTMLElement;
|
|
91
|
+
if (next && row.contains(next)) return;
|
|
92
|
+
showActions = false;
|
|
93
|
+
showReactionPicker = false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Escape closes the action bar / picker when open. Bound on the window (gated
|
|
97
|
+
// on open state) rather than the noninteractive row element so Svelte's
|
|
98
|
+
// a11y_no_noninteractive_element_interactions rule stays satisfied.
|
|
99
|
+
function handleEscape(event: KeyboardEvent) {
|
|
100
|
+
if (event.key === 'Escape') {
|
|
101
|
+
showReactionPicker = false;
|
|
102
|
+
showActions = false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
67
105
|
</script>
|
|
68
106
|
|
|
107
|
+
<svelte:window
|
|
108
|
+
onkeydown={showActions || showReactionPicker ? handleEscape : undefined}
|
|
109
|
+
/>
|
|
110
|
+
|
|
69
111
|
{#if message.messageType === 'system' || message.messageType === 'action'}
|
|
70
112
|
<div class="message-item message-item--system">
|
|
71
113
|
<MessageBubble content={message.content} isOwn={false} variant="system" />
|
|
@@ -78,6 +120,7 @@ function toggleReactionPicker() {
|
|
|
78
120
|
aria-label={t(M['chat.message_item.message_from'], { name: message.senderName })}
|
|
79
121
|
onmouseenter={() => showActions = true}
|
|
80
122
|
onmouseleave={() => { showActions = false; showReactionPicker = false; }}
|
|
123
|
+
onfocusout={handleFocusOut}
|
|
81
124
|
>
|
|
82
125
|
{#if !isOwn}
|
|
83
126
|
<div class="message-item__avatar">
|
|
@@ -151,8 +194,27 @@ function toggleReactionPicker() {
|
|
|
151
194
|
</div>
|
|
152
195
|
</div>
|
|
153
196
|
|
|
197
|
+
{#if hasActions}
|
|
198
|
+
<button
|
|
199
|
+
class="message-item__more-btn"
|
|
200
|
+
type="button"
|
|
201
|
+
onclick={openActions}
|
|
202
|
+
onfocus={openActions}
|
|
203
|
+
aria-label={t(M['chat.message_item.more_actions'])}
|
|
204
|
+
aria-expanded={showActions}
|
|
205
|
+
aria-controls={actionsId}
|
|
206
|
+
>
|
|
207
|
+
<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><circle cx="3" cy="8" r="1.3" fill="currentColor" /><circle cx="8" cy="8" r="1.3" fill="currentColor" /><circle cx="13" cy="8" r="1.3" fill="currentColor" /></svg>
|
|
208
|
+
</button>
|
|
209
|
+
{/if}
|
|
210
|
+
|
|
154
211
|
{#if showActions}
|
|
155
|
-
<div
|
|
212
|
+
<div
|
|
213
|
+
class="message-item__actions"
|
|
214
|
+
role="group"
|
|
215
|
+
id={actionsId}
|
|
216
|
+
aria-label={t(M['chat.message_item.more_actions'])}
|
|
217
|
+
>
|
|
156
218
|
{#if onreact}
|
|
157
219
|
<button
|
|
158
220
|
class="message-item__action-btn"
|
|
@@ -379,6 +441,49 @@ function toggleReactionPicker() {
|
|
|
379
441
|
font-style: italic;
|
|
380
442
|
}
|
|
381
443
|
|
|
444
|
+
.message-item__more-btn {
|
|
445
|
+
display: flex;
|
|
446
|
+
align-items: center;
|
|
447
|
+
justify-content: center;
|
|
448
|
+
width: 26px;
|
|
449
|
+
height: 26px;
|
|
450
|
+
position: absolute;
|
|
451
|
+
top: 0;
|
|
452
|
+
right: var(--smrt-spacing-4, 1rem);
|
|
453
|
+
border: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
|
|
454
|
+
border-radius: var(--smrt-radius-small, 0.25rem);
|
|
455
|
+
background: var(--smrt-color-surface, #ffffff);
|
|
456
|
+
color: var(--smrt-color-on-surface-variant, #43474e);
|
|
457
|
+
cursor: pointer;
|
|
458
|
+
opacity: 0;
|
|
459
|
+
transition: opacity var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.message-item--own .message-item__more-btn {
|
|
463
|
+
right: auto;
|
|
464
|
+
left: var(--smrt-spacing-4, 1rem);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/* Reveal the keyboard/touch affordance on hover OR when focus is inside the
|
|
468
|
+
row, so it is never strictly hover-only (a11y blocker, T2 #1391). Always
|
|
469
|
+
visible to coarse pointers (touch) which cannot hover. */
|
|
470
|
+
.message-item:hover .message-item__more-btn,
|
|
471
|
+
.message-item:focus-within .message-item__more-btn {
|
|
472
|
+
opacity: 1;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.message-item__more-btn:focus-visible {
|
|
476
|
+
opacity: 1;
|
|
477
|
+
outline: 2px solid var(--smrt-color-primary, #005ac1);
|
|
478
|
+
outline-offset: 1px;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
@media (hover: none) {
|
|
482
|
+
.message-item__more-btn {
|
|
483
|
+
opacity: 1;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
382
487
|
.message-item__actions {
|
|
383
488
|
display: flex;
|
|
384
489
|
align-items: center;
|
|
@@ -391,6 +496,7 @@ function toggleReactionPicker() {
|
|
|
391
496
|
border-radius: var(--smrt-radius-small, 0.25rem);
|
|
392
497
|
padding: var(--smrt-spacing-1, 4px);
|
|
393
498
|
box-shadow: var(--smrt-elevation-1, 0 1px 3px rgba(0, 0, 0, 0.12));
|
|
499
|
+
z-index: 1;
|
|
394
500
|
}
|
|
395
501
|
|
|
396
502
|
.message-item--own .message-item__actions {
|
|
@@ -428,4 +534,12 @@ function toggleReactionPicker() {
|
|
|
428
534
|
right: auto;
|
|
429
535
|
left: var(--smrt-spacing-4, 1rem);
|
|
430
536
|
}
|
|
537
|
+
|
|
538
|
+
@media (prefers-reduced-motion: reduce) {
|
|
539
|
+
.message-item__more-btn,
|
|
540
|
+
.message-item__action-btn,
|
|
541
|
+
.message-item__reaction {
|
|
542
|
+
transition: none;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
431
545
|
</style>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageItem.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/messages/MessageItem.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAMtD,MAAM,WAAW,KAAK;IACpB,6BAA6B;IAC7B,OAAO,EAAE,eAAe,CAAC;IACzB,wDAAwD;IACxD,KAAK,EAAE,OAAO,CAAC;IACf,qBAAqB;IACrB,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,qBAAqB;IACrB,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,oBAAoB;IACpB,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,sBAAsB;IACtB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;
|
|
1
|
+
{"version":3,"file":"MessageItem.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/messages/MessageItem.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAMtD,MAAM,WAAW,KAAK;IACpB,6BAA6B;IAC7B,OAAO,EAAE,eAAe,CAAC;IACzB,wDAAwD;IACxD,KAAK,EAAE,OAAO,CAAC;IACf,qBAAqB;IACrB,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,qBAAqB;IACrB,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,oBAAoB;IACpB,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,sBAAsB;IACtB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAuMD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* Component coverage for MessageItem via the shared S11 harness (#1416).
|
|
4
|
+
*
|
|
5
|
+
* Regression for the T2 #1391 a11y blocker: the per-message actions
|
|
6
|
+
* (reply/edit/delete/react) used to be revealed ONLY by mouseenter/mouseleave,
|
|
7
|
+
* so keyboard- and touch-only users could never reach them. There must now be a
|
|
8
|
+
* keyboard-reachable "more actions" button that toggles the action bar, and
|
|
9
|
+
* focusing into the row must reveal the actions.
|
|
10
|
+
*/
|
|
11
|
+
import { expectNoA11yViolations, render, screen, userEvent, } from '@happyvertical/smrt-vitest/svelte';
|
|
12
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
13
|
+
import MessageItem from '../MessageItem.svelte';
|
|
14
|
+
function makeMessage(overrides = {}) {
|
|
15
|
+
return {
|
|
16
|
+
id: 'm1',
|
|
17
|
+
roomId: 'room-1',
|
|
18
|
+
threadId: null,
|
|
19
|
+
senderProfileId: 'p1',
|
|
20
|
+
senderName: 'Ada',
|
|
21
|
+
senderAvatarUrl: undefined,
|
|
22
|
+
agentSessionId: null,
|
|
23
|
+
content: 'Hello there',
|
|
24
|
+
messageType: 'text',
|
|
25
|
+
role: 'user',
|
|
26
|
+
isEdited: false,
|
|
27
|
+
isDeleted: false,
|
|
28
|
+
replyTo: null,
|
|
29
|
+
reactions: [],
|
|
30
|
+
attachments: [],
|
|
31
|
+
toolCallData: null,
|
|
32
|
+
createdAt: new Date('2026-01-01T00:00:00Z'),
|
|
33
|
+
...overrides,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
describe('MessageItem', () => {
|
|
37
|
+
it('renders a keyboard-reachable "more actions" button when actions are available', () => {
|
|
38
|
+
render(MessageItem, {
|
|
39
|
+
props: {
|
|
40
|
+
message: makeMessage(),
|
|
41
|
+
isOwn: false,
|
|
42
|
+
onreply: vi.fn(),
|
|
43
|
+
onreact: vi.fn(),
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
const moreBtn = screen.getByRole('button', { name: 'Message actions' });
|
|
47
|
+
expect(moreBtn).toBeInTheDocument();
|
|
48
|
+
// A real <button> is inherently in the tab order (no tabindex="-1").
|
|
49
|
+
expect(moreBtn.getAttribute('tabindex')).not.toBe('-1');
|
|
50
|
+
});
|
|
51
|
+
it('reveals the action bar via the more-actions button without any mouse hover', async () => {
|
|
52
|
+
const onreply = vi.fn();
|
|
53
|
+
render(MessageItem, {
|
|
54
|
+
props: {
|
|
55
|
+
message: makeMessage(),
|
|
56
|
+
isOwn: false,
|
|
57
|
+
onreply,
|
|
58
|
+
onreact: vi.fn(),
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
// Actions are hidden until requested — no hover, no focus yet.
|
|
62
|
+
expect(screen.queryByRole('button', { name: 'Reply' })).toBeNull();
|
|
63
|
+
await userEvent.click(screen.getByRole('button', { name: 'Message actions' }));
|
|
64
|
+
// The action bar is now reachable; clicking Reply invokes the callback.
|
|
65
|
+
const replyBtn = await screen.findByRole('button', { name: 'Reply' });
|
|
66
|
+
await userEvent.click(replyBtn);
|
|
67
|
+
expect(onreply).toHaveBeenCalledWith('m1');
|
|
68
|
+
});
|
|
69
|
+
it("exposes edit/delete actions for the user's own message", async () => {
|
|
70
|
+
const onedit = vi.fn();
|
|
71
|
+
const ondelete = vi.fn();
|
|
72
|
+
render(MessageItem, {
|
|
73
|
+
props: {
|
|
74
|
+
message: makeMessage(),
|
|
75
|
+
isOwn: true,
|
|
76
|
+
onedit,
|
|
77
|
+
ondelete,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
await userEvent.click(screen.getByRole('button', { name: 'Message actions' }));
|
|
81
|
+
await userEvent.click(await screen.findByRole('button', { name: 'Edit' }));
|
|
82
|
+
expect(onedit).toHaveBeenCalledWith('m1');
|
|
83
|
+
// The bar stays open while focus remains inside the row, so Delete is
|
|
84
|
+
// reachable in the same interaction.
|
|
85
|
+
await userEvent.click(screen.getByRole('button', { name: 'Delete' }));
|
|
86
|
+
expect(ondelete).toHaveBeenCalledWith('m1');
|
|
87
|
+
});
|
|
88
|
+
it('does not render the more-actions affordance when no action callbacks are provided', () => {
|
|
89
|
+
render(MessageItem, {
|
|
90
|
+
props: {
|
|
91
|
+
message: makeMessage(),
|
|
92
|
+
isOwn: false,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
expect(screen.queryByRole('button', { name: 'Message actions' })).toBeNull();
|
|
96
|
+
});
|
|
97
|
+
it('is axe-clean with the action affordance present', async () => {
|
|
98
|
+
const { container } = render(MessageItem, {
|
|
99
|
+
props: {
|
|
100
|
+
message: makeMessage(),
|
|
101
|
+
isOwn: false,
|
|
102
|
+
onreply: vi.fn(),
|
|
103
|
+
onreact: vi.fn(),
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
await expectNoA11yViolations(container);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -19,9 +19,11 @@ export interface Props {
|
|
|
19
19
|
onselect: (id: string) => void;
|
|
20
20
|
/** Whether the popup is visible */
|
|
21
21
|
isVisible: boolean;
|
|
22
|
+
/** Dismiss the popup (e.g. Escape pressed) without selecting */
|
|
23
|
+
oncancel?: () => void;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
const { query, suggestions, onselect, isVisible }: Props = $props();
|
|
26
|
+
const { query, suggestions, onselect, isVisible, oncancel }: Props = $props();
|
|
25
27
|
|
|
26
28
|
let activeIndex = $state(0);
|
|
27
29
|
|
|
@@ -50,6 +52,8 @@ function handleKeydown(event: KeyboardEvent) {
|
|
|
50
52
|
onselect(suggestions[activeIndex].id);
|
|
51
53
|
break;
|
|
52
54
|
case 'Escape':
|
|
55
|
+
event.preventDefault();
|
|
56
|
+
oncancel?.();
|
|
53
57
|
break;
|
|
54
58
|
}
|
|
55
59
|
}
|
|
@@ -11,6 +11,8 @@ export interface Props {
|
|
|
11
11
|
onselect: (id: string) => void;
|
|
12
12
|
/** Whether the popup is visible */
|
|
13
13
|
isVisible: boolean;
|
|
14
|
+
/** Dismiss the popup (e.g. Escape pressed) without selecting */
|
|
15
|
+
oncancel?: () => void;
|
|
14
16
|
}
|
|
15
17
|
declare const MentionAutocomplete: import("svelte").Component<Props, {}, "">;
|
|
16
18
|
type MentionAutocomplete = ReturnType<typeof MentionAutocomplete>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MentionAutocomplete.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/shared/MentionAutocomplete.svelte.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,KAAK;IACpB,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,WAAW,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrE,0BAA0B;IAC1B,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,mCAAmC;IACnC,SAAS,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"MentionAutocomplete.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/shared/MentionAutocomplete.svelte.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,KAAK;IACpB,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,WAAW,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrE,0BAA0B;IAC1B,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,mCAAmC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAiFD,QAAA,MAAM,mBAAmB,2CAAwC,CAAC;AAClE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}
|
|
@@ -42,15 +42,15 @@ const labelText: Record<string, string> = {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
.presence__dot.online {
|
|
45
|
-
background: var(--smrt-color-success, #
|
|
45
|
+
background: var(--smrt-color-success, #1e8e3e);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
.presence__dot.away {
|
|
49
|
-
background: var(--smrt-color-warning, #
|
|
49
|
+
background: var(--smrt-color-warning, #f9ab00);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
.presence__dot.dnd {
|
|
53
|
-
background: var(--smrt-color-error, #
|
|
53
|
+
background: var(--smrt-color-error, #b3261e);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
.presence__dot.offline {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* Component coverage for MentionAutocomplete via the shared S11 harness (#1416).
|
|
4
|
+
*
|
|
5
|
+
* Regression for T2 #1391: `case 'Escape': break;` was a no-op, so the
|
|
6
|
+
* suggestion popup could not be dismissed from the keyboard. Escape must now
|
|
7
|
+
* invoke the `oncancel` callback so a parent can close the popup.
|
|
8
|
+
*/
|
|
9
|
+
import { expectNoA11yViolations, render, screen, userEvent, } from '@happyvertical/smrt-vitest/svelte';
|
|
10
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
11
|
+
import MentionAutocomplete from '../MentionAutocomplete.svelte';
|
|
12
|
+
const SUGGESTIONS = [
|
|
13
|
+
{ id: 'p1', name: 'Ada Lovelace' },
|
|
14
|
+
{ id: 'p2', name: 'Alan Turing' },
|
|
15
|
+
];
|
|
16
|
+
describe('MentionAutocomplete', () => {
|
|
17
|
+
it('invokes oncancel when Escape is pressed while visible', async () => {
|
|
18
|
+
const oncancel = vi.fn();
|
|
19
|
+
render(MentionAutocomplete, {
|
|
20
|
+
props: {
|
|
21
|
+
query: 'a',
|
|
22
|
+
suggestions: SUGGESTIONS,
|
|
23
|
+
onselect: vi.fn(),
|
|
24
|
+
isVisible: true,
|
|
25
|
+
oncancel,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
await userEvent.keyboard('{Escape}');
|
|
29
|
+
expect(oncancel).toHaveBeenCalledTimes(1);
|
|
30
|
+
});
|
|
31
|
+
it('does not throw on Escape when no oncancel is provided', async () => {
|
|
32
|
+
render(MentionAutocomplete, {
|
|
33
|
+
props: {
|
|
34
|
+
query: 'a',
|
|
35
|
+
suggestions: SUGGESTIONS,
|
|
36
|
+
onselect: vi.fn(),
|
|
37
|
+
isVisible: true,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
await expect(userEvent.keyboard('{Escape}')).resolves.not.toThrow();
|
|
41
|
+
});
|
|
42
|
+
it('selects the active suggestion on Enter', async () => {
|
|
43
|
+
const onselect = vi.fn();
|
|
44
|
+
render(MentionAutocomplete, {
|
|
45
|
+
props: {
|
|
46
|
+
query: 'a',
|
|
47
|
+
suggestions: SUGGESTIONS,
|
|
48
|
+
onselect,
|
|
49
|
+
isVisible: true,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
await userEvent.keyboard('{Enter}');
|
|
53
|
+
expect(onselect).toHaveBeenCalledWith('p1');
|
|
54
|
+
});
|
|
55
|
+
it('is axe-clean', async () => {
|
|
56
|
+
const { container } = render(MentionAutocomplete, {
|
|
57
|
+
props: {
|
|
58
|
+
query: 'a',
|
|
59
|
+
suggestions: SUGGESTIONS,
|
|
60
|
+
onselect: vi.fn(),
|
|
61
|
+
isVisible: true,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
await expectNoA11yViolations(container);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -24,6 +24,7 @@ export declare const M: {
|
|
|
24
24
|
readonly 'chat.message_item.reply': "chat.message_item.reply";
|
|
25
25
|
readonly 'chat.message_item.edit': "chat.message_item.edit";
|
|
26
26
|
readonly 'chat.message_item.delete': "chat.message_item.delete";
|
|
27
|
+
readonly 'chat.message_item.more_actions': "chat.message_item.more_actions";
|
|
27
28
|
readonly 'chat.message_list.messages_label': "chat.message_list.messages_label";
|
|
28
29
|
readonly 'chat.message_list.no_messages': "chat.message_list.no_messages";
|
|
29
30
|
readonly 'chat.thread_panel.thread_label': "chat.thread_panel.thread_label";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.messages.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.messages.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,CAAC
|
|
1
|
+
{"version":3,"file":"i18n.messages.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.messages.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwEZ,CAAC"}
|
|
@@ -38,6 +38,7 @@ export const M = defineMessages({
|
|
|
38
38
|
'chat.message_item.reply': 'Reply',
|
|
39
39
|
'chat.message_item.edit': 'Edit',
|
|
40
40
|
'chat.message_item.delete': 'Delete',
|
|
41
|
+
'chat.message_item.more_actions': 'Message actions',
|
|
41
42
|
// MessageList
|
|
42
43
|
'chat.message_list.messages_label': 'Messages',
|
|
43
44
|
'chat.message_list.no_messages': 'No messages yet',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.1",
|
|
4
4
|
"description": "Chat rooms, DMs, threads, and agent conversations for the SMRT framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -55,10 +55,10 @@
|
|
|
55
55
|
"access": "public"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@happyvertical/smrt-
|
|
59
|
-
"@happyvertical/smrt-types": "0.
|
|
60
|
-
"@happyvertical/smrt-
|
|
61
|
-
"@happyvertical/smrt-tenancy": "0.
|
|
58
|
+
"@happyvertical/smrt-ui": "0.31.1",
|
|
59
|
+
"@happyvertical/smrt-types": "0.31.1",
|
|
60
|
+
"@happyvertical/smrt-core": "0.31.1",
|
|
61
|
+
"@happyvertical/smrt-tenancy": "0.31.1"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"svelte": "^5.18.0"
|
|
@@ -78,9 +78,9 @@
|
|
|
78
78
|
"typescript": "^5.9.3",
|
|
79
79
|
"vite": "^7.3.1",
|
|
80
80
|
"vitest": "^4.0.17",
|
|
81
|
-
"@happyvertical/smrt-agents": "0.
|
|
82
|
-
"@happyvertical/smrt-profiles": "0.
|
|
83
|
-
"@happyvertical/smrt-vitest": "0.
|
|
81
|
+
"@happyvertical/smrt-agents": "0.31.1",
|
|
82
|
+
"@happyvertical/smrt-profiles": "0.31.1",
|
|
83
|
+
"@happyvertical/smrt-vitest": "0.31.1"
|
|
84
84
|
},
|
|
85
85
|
"scripts": {
|
|
86
86
|
"build": "vite build --mode library && svelte-package -i src/svelte -o dist/svelte --tsconfig tsconfig.svelte.json",
|