@happyvertical/smrt-chat 0.34.6 → 0.34.7
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/manifest.json +2 -2
- package/dist/smrt-knowledge.json +4 -4
- package/dist/svelte/components/agent/AgentChat.svelte +14 -26
- package/dist/svelte/components/agent/AgentChat.svelte.d.ts.map +1 -1
- package/dist/svelte/components/dialogs/RoomCreateDialog.svelte +20 -50
- package/dist/svelte/components/dialogs/RoomCreateDialog.svelte.d.ts.map +1 -1
- package/dist/svelte/components/dialogs/SearchMessages.svelte +28 -12
- package/dist/svelte/components/dialogs/SearchMessages.svelte.d.ts.map +1 -1
- package/dist/svelte/components/messages/MessageInput.svelte +17 -28
- package/dist/svelte/components/messages/MessageInput.svelte.d.ts.map +1 -1
- package/dist/svelte/components/messages/MessageItem.svelte +7 -3
- package/dist/svelte/components/messages/MessageItem.svelte.d.ts.map +1 -1
- package/dist/svelte/components/shared/FileUpload.svelte +1 -0
- package/dist/svelte/components/shared/FileUpload.svelte.d.ts.map +1 -1
- package/dist/svelte/components/shared/ReactionPicker.svelte +15 -53
- package/dist/svelte/components/shared/ReactionPicker.svelte.d.ts.map +1 -1
- package/dist/svelte/components/tabs/MiniChat.svelte +15 -25
- package/dist/svelte/components/tabs/MiniChat.svelte.d.ts.map +1 -1
- package/dist/svelte/index.d.ts +1 -2
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +5 -4
- package/package.json +9 -9
- package/dist/svelte/components/shared/MessageBubble.svelte +0 -81
- package/dist/svelte/components/shared/MessageBubble.svelte.d.ts +0 -16
- package/dist/svelte/components/shared/MessageBubble.svelte.d.ts.map +0 -1
- package/dist/svelte/components/shared/TypingIndicator.svelte +0 -90
- package/dist/svelte/components/shared/TypingIndicator.svelte.d.ts +0 -12
- package/dist/svelte/components/shared/TypingIndicator.svelte.d.ts.map +0 -1
- package/dist/svelte/components/shared/__tests__/MessageBubble.test.js +0 -21
- package/dist/svelte/components/shared/__tests__/TypingIndicator.test.js +0 -27
package/dist/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1.0.0",
|
|
3
|
-
"timestamp":
|
|
3
|
+
"timestamp": 1782290164480,
|
|
4
4
|
"packageName": "@happyvertical/smrt-chat",
|
|
5
|
-
"packageVersion": "0.34.
|
|
5
|
+
"packageVersion": "0.34.7",
|
|
6
6
|
"objects": {
|
|
7
7
|
"@happyvertical/smrt-chat:AgentSessionCollection": {
|
|
8
8
|
"name": "agentsessioncollection",
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-06-
|
|
3
|
+
"generatedAt": "2026-06-24T08:36:04.806Z",
|
|
4
4
|
"packageName": "@happyvertical/smrt-chat",
|
|
5
|
-
"packageVersion": "0.34.
|
|
5
|
+
"packageVersion": "0.34.7",
|
|
6
6
|
"sourceManifestPath": "dist/manifest.json",
|
|
7
7
|
"agentDocPath": "AGENTS.md",
|
|
8
8
|
"sourceHashes": {
|
|
9
|
-
"manifest": "
|
|
10
|
-
"packageJson": "
|
|
9
|
+
"manifest": "eb7dc5e132fb34b6c1588f1cec335f9f087d782e8762992c7dc7124023353fe3",
|
|
10
|
+
"packageJson": "ce14941c197b179f275a91e466bea275c71aa6cacb4aedd1d6f0b3a01ff53861",
|
|
11
11
|
"agents": "e0a8c6788fbd48c2619a353295d4538e9ff6863d483bcd354af522fb7c6a31bc"
|
|
12
12
|
},
|
|
13
13
|
"exports": [
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Agent messages are styled differently from user messages.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { Form, Textarea } from '@happyvertical/smrt-ui/forms';
|
|
8
9
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
9
10
|
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
10
11
|
import { M } from '../../i18n.js';
|
|
@@ -92,13 +93,13 @@ $effect(() => {
|
|
|
92
93
|
</script>
|
|
93
94
|
|
|
94
95
|
<div class="agent-chat" aria-label={t(M['chat.agent_chat.conversation'])}>
|
|
95
|
-
<
|
|
96
|
+
<Form class="agent-chat__input-bar" onsubmit={(e) => handleSubmit(e)}>
|
|
96
97
|
{#if !isActive}
|
|
97
98
|
<div class="agent-chat__inactive-notice">
|
|
98
99
|
{t(M['chat.agent_chat.inactive_notice'], { status: session.status })}
|
|
99
100
|
</div>
|
|
100
101
|
{:else}
|
|
101
|
-
<
|
|
102
|
+
<Textarea
|
|
102
103
|
class="agent-chat__input"
|
|
103
104
|
placeholder={t(M['chat.agent_chat.input_placeholder'])}
|
|
104
105
|
bind:value={inputValue}
|
|
@@ -106,7 +107,7 @@ $effect(() => {
|
|
|
106
107
|
rows={1}
|
|
107
108
|
disabled={!isActive}
|
|
108
109
|
aria-label={t(M['chat.agent_chat.message_input'])}
|
|
109
|
-
|
|
110
|
+
/>
|
|
110
111
|
<Button
|
|
111
112
|
variant="ghost"
|
|
112
113
|
class="agent-chat__send-btn"
|
|
@@ -119,7 +120,7 @@ $effect(() => {
|
|
|
119
120
|
</svg>
|
|
120
121
|
</Button>
|
|
121
122
|
{/if}
|
|
122
|
-
</
|
|
123
|
+
</Form>
|
|
123
124
|
|
|
124
125
|
<div
|
|
125
126
|
class="agent-chat__messages"
|
|
@@ -311,7 +312,8 @@ $effect(() => {
|
|
|
311
312
|
color: var(--smrt-color-outline, #74777f);
|
|
312
313
|
}
|
|
313
314
|
|
|
314
|
-
.
|
|
315
|
+
/* :global() targets the Form primitive's rendered <form> (see #1589 scoping trap). */
|
|
316
|
+
:global(.agent-chat__input-bar) {
|
|
315
317
|
display: flex;
|
|
316
318
|
align-items: flex-end;
|
|
317
319
|
gap: var(--smrt-spacing-2, 8px);
|
|
@@ -329,32 +331,18 @@ $effect(() => {
|
|
|
329
331
|
font-style: italic;
|
|
330
332
|
}
|
|
331
333
|
|
|
332
|
-
|
|
334
|
+
/* Layout for the Textarea primitive's rendered <textarea> (tokenised styling
|
|
335
|
+
comes from the primitive; this only sizes it within the input bar). */
|
|
336
|
+
:global(.agent-chat__input) {
|
|
333
337
|
flex: 1;
|
|
334
|
-
border: none;
|
|
335
|
-
border-radius: var(--smrt-radius-md, 8px);
|
|
336
|
-
padding: var(--smrt-spacing-2, 8px) var(--smrt-spacing-3, 12px);
|
|
337
|
-
font-size: var(--smrt-typography-body-medium-size, 0.8125rem);
|
|
338
|
-
line-height: var(--smrt-typography-body-medium-line-height, 1.4);
|
|
339
|
-
color: var(--smrt-color-on-surface, #1a1c1e);
|
|
340
|
-
background: var(--smrt-color-surface-container-low, #f7f7fb);
|
|
341
338
|
resize: none;
|
|
342
|
-
outline: none;
|
|
343
339
|
min-height: 34px;
|
|
344
340
|
max-height: 80px;
|
|
345
341
|
}
|
|
346
342
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
.agent-chat__input::placeholder {
|
|
353
|
-
color: var(--smrt-color-outline, #74777f);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/* :global() pierces into the Button child's rendered <button> (see #1589). */
|
|
357
|
-
.agent-chat__input-bar :global(.agent-chat__send-btn) {
|
|
343
|
+
/* :global() pierces into the Button child's rendered <button> (see #1589).
|
|
344
|
+
The ancestor is the Form primitive's <form>, also global. */
|
|
345
|
+
:global(.agent-chat__input-bar .agent-chat__send-btn) {
|
|
358
346
|
width: 34px;
|
|
359
347
|
height: 34px;
|
|
360
348
|
padding: 0;
|
|
@@ -365,7 +353,7 @@ $effect(() => {
|
|
|
365
353
|
transition: opacity 150ms;
|
|
366
354
|
}
|
|
367
355
|
|
|
368
|
-
.agent-chat__input-bar
|
|
356
|
+
:global(.agent-chat__input-bar .agent-chat__send-btn:not(:disabled):hover) {
|
|
369
357
|
background: var(--smrt-color-primary, #005ac1);
|
|
370
358
|
opacity: 0.85;
|
|
371
359
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentChat.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/agent/AgentChat.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AgentChat.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/agent/AgentChat.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EAEhB,MAAM,gBAAgB,CAAC;AAMxB,MAAM,WAAW,KAAK;IACpB,yBAAyB;IACzB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,oCAAoC;IACpC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,gCAAgC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IACzB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kCAAkC;IAClC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,4CAA4C;IAC5C,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,2BAA2B;IAC3B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AA0KD,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* hand-rolled backdrop it replaced had none of those.
|
|
9
9
|
*/
|
|
10
10
|
import { Modal } from '@happyvertical/smrt-ui/feedback';
|
|
11
|
+
import { Form, Input, Textarea } from '@happyvertical/smrt-ui/forms';
|
|
11
12
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
12
13
|
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
13
14
|
import { M } from '../../i18n.js';
|
|
@@ -32,7 +33,6 @@ let { isOpen, onclose, oncreate }: Props = $props();
|
|
|
32
33
|
let name = $state('');
|
|
33
34
|
let roomType = $state('public');
|
|
34
35
|
let description = $state('');
|
|
35
|
-
let nameInput = $state<HTMLInputElement | null>(null);
|
|
36
36
|
|
|
37
37
|
const canCreate = $derived(name.trim().length > 0);
|
|
38
38
|
|
|
@@ -81,10 +81,15 @@ function resetForm() {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
// Modal traps focus and moves it to the dialog itself; nudge initial focus to
|
|
84
|
-
// the name field once the dialog is open and the input is mounted.
|
|
84
|
+
// the name field once the dialog is open and the input is mounted. The Input
|
|
85
|
+
// primitive doesn't expose its inner element, so resolve it by id.
|
|
85
86
|
$effect(() => {
|
|
86
|
-
if (isOpen &&
|
|
87
|
-
|
|
87
|
+
if (isOpen && typeof document !== 'undefined') {
|
|
88
|
+
// Defer a frame so the Modal body (and the name input) is mounted first.
|
|
89
|
+
const raf = requestAnimationFrame(() => {
|
|
90
|
+
document.getElementById('room-name')?.focus();
|
|
91
|
+
});
|
|
92
|
+
return () => cancelAnimationFrame(raf);
|
|
88
93
|
}
|
|
89
94
|
});
|
|
90
95
|
</script>
|
|
@@ -96,7 +101,7 @@ $effect(() => {
|
|
|
96
101
|
size="md"
|
|
97
102
|
ariaLabel={t(M['chat.room_create_dialog.title'])}
|
|
98
103
|
>
|
|
99
|
-
<
|
|
104
|
+
<Form
|
|
100
105
|
class="dialog__form"
|
|
101
106
|
onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}
|
|
102
107
|
>
|
|
@@ -104,14 +109,12 @@ $effect(() => {
|
|
|
104
109
|
<label class="field__label" for="room-name">
|
|
105
110
|
{t(M['chat.room_create_dialog.room_name'])} <span class="field__required" aria-label={t(M['chat.room_create_dialog.required'])}>*</span>
|
|
106
111
|
</label>
|
|
107
|
-
<
|
|
108
|
-
bind:this={nameInput}
|
|
112
|
+
<Input
|
|
109
113
|
bind:value={name}
|
|
110
114
|
id="room-name"
|
|
111
115
|
type="text"
|
|
112
|
-
class="field__input"
|
|
113
116
|
placeholder={t(M['chat.room_create_dialog.name_placeholder'])}
|
|
114
|
-
maxlength=
|
|
117
|
+
maxlength={100}
|
|
115
118
|
autocomplete="off"
|
|
116
119
|
/>
|
|
117
120
|
</div>
|
|
@@ -124,6 +127,7 @@ $effect(() => {
|
|
|
124
127
|
class="type-option"
|
|
125
128
|
class:type-option--selected={roomType === option.value}
|
|
126
129
|
>
|
|
130
|
+
<!-- raw-primitive-allow: native radio for single-choice room-type selection; no Provider-free radio primitive (Toggle is a switch with different semantics, CheckboxInput requires a Provider) -->
|
|
127
131
|
<input
|
|
128
132
|
type="radio"
|
|
129
133
|
name="roomType"
|
|
@@ -142,21 +146,20 @@ $effect(() => {
|
|
|
142
146
|
|
|
143
147
|
<div class="field">
|
|
144
148
|
<label class="field__label" for="room-description">Description</label>
|
|
145
|
-
<
|
|
149
|
+
<Textarea
|
|
146
150
|
bind:value={description}
|
|
147
151
|
id="room-description"
|
|
148
|
-
class="field__textarea"
|
|
149
152
|
placeholder={t(M['chat.room_create_dialog.description_placeholder'])}
|
|
150
|
-
rows=
|
|
151
|
-
maxlength=
|
|
152
|
-
|
|
153
|
+
rows={3}
|
|
154
|
+
maxlength={500}
|
|
155
|
+
/>
|
|
153
156
|
<span class="field__hint">{description.length}/500</span>
|
|
154
157
|
</div>
|
|
155
158
|
|
|
156
159
|
<!-- Hidden submit keeps Enter-to-submit working inside the Modal body. -->
|
|
157
160
|
<!-- raw-primitive-allow: off-screen aria-hidden type=submit element with tabindex=-1 used only to enable native Enter-to-submit inside the Modal body; intentionally non-interactive and not a visible action button -->
|
|
158
161
|
<button type="submit" class="visually-hidden" tabindex="-1" disabled={!canCreate} aria-hidden="true"></button>
|
|
159
|
-
</
|
|
162
|
+
</Form>
|
|
160
163
|
|
|
161
164
|
{#snippet footer()}
|
|
162
165
|
<Button
|
|
@@ -178,7 +181,8 @@ $effect(() => {
|
|
|
178
181
|
</Modal>
|
|
179
182
|
|
|
180
183
|
<style>
|
|
181
|
-
.
|
|
184
|
+
/* :global() targets the Form primitive's rendered <form> (see #1589 scoping trap). */
|
|
185
|
+
:global(.dialog__form) {
|
|
182
186
|
display: flex;
|
|
183
187
|
flex-direction: column;
|
|
184
188
|
gap: 1.25rem;
|
|
@@ -214,40 +218,6 @@ $effect(() => {
|
|
|
214
218
|
color: var(--smrt-color-error, #b3261e);
|
|
215
219
|
}
|
|
216
220
|
|
|
217
|
-
.field__input {
|
|
218
|
-
padding: 0.75rem;
|
|
219
|
-
font: var(--smrt-typography-body-large-font, 1rem / 1.5 sans-serif);
|
|
220
|
-
font-family: inherit;
|
|
221
|
-
border: 1px solid var(--smrt-color-outline, #74777f);
|
|
222
|
-
border-radius: var(--smrt-radius-medium, 0.5rem);
|
|
223
|
-
background: var(--smrt-color-surface, #fefbff);
|
|
224
|
-
color: var(--smrt-color-on-surface, #1a1c1e);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.field__input:focus {
|
|
228
|
-
outline: none;
|
|
229
|
-
border-color: var(--smrt-color-primary, #005ac1);
|
|
230
|
-
box-shadow: 0 0 0 1px var(--smrt-color-primary, #005ac1);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
.field__textarea {
|
|
234
|
-
padding: 0.75rem;
|
|
235
|
-
font: var(--smrt-typography-body-large-font, 1rem / 1.5 sans-serif);
|
|
236
|
-
font-family: inherit;
|
|
237
|
-
border: 1px solid var(--smrt-color-outline, #74777f);
|
|
238
|
-
border-radius: var(--smrt-radius-medium, 0.5rem);
|
|
239
|
-
background: var(--smrt-color-surface, #fefbff);
|
|
240
|
-
color: var(--smrt-color-on-surface, #1a1c1e);
|
|
241
|
-
resize: vertical;
|
|
242
|
-
min-height: 80px;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
.field__textarea:focus {
|
|
246
|
-
outline: none;
|
|
247
|
-
border-color: var(--smrt-color-primary, #005ac1);
|
|
248
|
-
box-shadow: 0 0 0 1px var(--smrt-color-primary, #005ac1);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
221
|
.field__hint {
|
|
252
222
|
font: var(--smrt-typography-body-small-font, 0.75rem / 1.25 sans-serif);
|
|
253
223
|
color: var(--smrt-color-on-surface-variant, #43474e);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RoomCreateDialog.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/dialogs/RoomCreateDialog.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RoomCreateDialog.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/dialogs/RoomCreateDialog.svelte.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,KAAK;IACpB,iCAAiC;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,mCAAmC;IACnC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,oCAAoC;IACpC,QAAQ,EAAE,CAAC,MAAM,EAAE;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;KACrB,KAAK,IAAI,CAAC;CACZ;AAgID,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* SearchMessages - Message search interface with results list
|
|
4
4
|
* Provides a search input and displays matching messages
|
|
5
5
|
*/
|
|
6
|
+
import { Form, Input } from '@happyvertical/smrt-ui/forms';
|
|
6
7
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
7
8
|
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
8
9
|
import { M } from '../../i18n.js';
|
|
@@ -26,11 +27,16 @@ export interface Props {
|
|
|
26
27
|
let { isOpen, onclose, onsearch, results, onselectresult }: Props = $props();
|
|
27
28
|
|
|
28
29
|
let query = $state('');
|
|
29
|
-
let searchInput = $state<HTMLInputElement | null>(null);
|
|
30
30
|
|
|
31
31
|
const hasResults = $derived(results.length > 0);
|
|
32
32
|
const hasQuery = $derived(query.trim().length > 0);
|
|
33
33
|
|
|
34
|
+
// The Input primitive doesn't expose its inner element, so resolve it by id.
|
|
35
|
+
function focusSearchInput() {
|
|
36
|
+
if (typeof document === 'undefined') return;
|
|
37
|
+
document.getElementById('search-messages-input')?.focus();
|
|
38
|
+
}
|
|
39
|
+
|
|
34
40
|
function handleSubmit() {
|
|
35
41
|
const trimmed = query.trim();
|
|
36
42
|
if (trimmed.length > 0) {
|
|
@@ -95,8 +101,10 @@ $effect(() => {
|
|
|
95
101
|
});
|
|
96
102
|
|
|
97
103
|
$effect(() => {
|
|
98
|
-
if (isOpen
|
|
99
|
-
|
|
104
|
+
if (isOpen) {
|
|
105
|
+
// Defer a frame so the search input is mounted before focusing.
|
|
106
|
+
const raf = requestAnimationFrame(focusSearchInput);
|
|
107
|
+
return () => cancelAnimationFrame(raf);
|
|
100
108
|
}
|
|
101
109
|
});
|
|
102
110
|
</script>
|
|
@@ -124,7 +132,7 @@ $effect(() => {
|
|
|
124
132
|
</Button>
|
|
125
133
|
</div>
|
|
126
134
|
|
|
127
|
-
<
|
|
135
|
+
<Form
|
|
128
136
|
class="search-form"
|
|
129
137
|
onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}
|
|
130
138
|
>
|
|
@@ -133,9 +141,9 @@ $effect(() => {
|
|
|
133
141
|
<circle cx="11" cy="11" r="8" />
|
|
134
142
|
<path d="m21 21-4.35-4.35" />
|
|
135
143
|
</svg>
|
|
136
|
-
<
|
|
137
|
-
bind:this={searchInput}
|
|
144
|
+
<Input
|
|
138
145
|
bind:value={query}
|
|
146
|
+
id="search-messages-input"
|
|
139
147
|
type="search"
|
|
140
148
|
class="search-input"
|
|
141
149
|
placeholder={t(M['chat.search_messages.input_placeholder'])}
|
|
@@ -148,7 +156,7 @@ $effect(() => {
|
|
|
148
156
|
size="sm"
|
|
149
157
|
class="clear-btn"
|
|
150
158
|
type="button"
|
|
151
|
-
onclick={() => { query = '';
|
|
159
|
+
onclick={() => { query = ''; focusSearchInput(); }}
|
|
152
160
|
aria-label={t(M['chat.search_messages.clear'])}
|
|
153
161
|
>
|
|
154
162
|
<svg class="clear-btn__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
@@ -158,7 +166,7 @@ $effect(() => {
|
|
|
158
166
|
</Button>
|
|
159
167
|
{/if}
|
|
160
168
|
</div>
|
|
161
|
-
</
|
|
169
|
+
</Form>
|
|
162
170
|
|
|
163
171
|
<div class="search-results" aria-label={t(M['chat.search_messages.results'])}>
|
|
164
172
|
{#if hasResults}
|
|
@@ -257,7 +265,8 @@ $effect(() => {
|
|
|
257
265
|
height: 1rem;
|
|
258
266
|
}
|
|
259
267
|
|
|
260
|
-
|
|
268
|
+
/* :global() targets the Form primitive's rendered <form> (see #1589 scoping trap). */
|
|
269
|
+
:global(.search-form) {
|
|
261
270
|
padding: 0.75rem 1rem;
|
|
262
271
|
border-bottom: 1px solid var(--smrt-color-outline-variant, #c4c6d0);
|
|
263
272
|
}
|
|
@@ -285,17 +294,24 @@ $effect(() => {
|
|
|
285
294
|
color: var(--smrt-color-on-surface-variant, #43474e);
|
|
286
295
|
}
|
|
287
296
|
|
|
288
|
-
.search-input
|
|
297
|
+
/* The Input primitive sits inside the bordered .search-input-wrap, which owns
|
|
298
|
+
the border + focus-within ring — so neutralize the primitive's own chrome
|
|
299
|
+
(border/background/padding/box-shadow) and let it render borderless. */
|
|
300
|
+
:global(.search-input) {
|
|
289
301
|
flex: 1;
|
|
290
302
|
border: none;
|
|
291
303
|
outline: none;
|
|
304
|
+
padding: 0;
|
|
292
305
|
background: transparent;
|
|
306
|
+
box-shadow: none;
|
|
293
307
|
font: var(--smrt-typography-body-medium-font, 0.875rem / 1.25 sans-serif);
|
|
294
308
|
color: var(--smrt-color-on-surface, #1a1c1e);
|
|
295
309
|
}
|
|
296
310
|
|
|
297
|
-
.search-input
|
|
298
|
-
|
|
311
|
+
:global(.search-input:focus) {
|
|
312
|
+
border: none;
|
|
313
|
+
outline: none;
|
|
314
|
+
box-shadow: none;
|
|
299
315
|
}
|
|
300
316
|
|
|
301
317
|
/* :global() pierces into the Button child's rendered <button> (see #1589). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchMessages.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/dialogs/SearchMessages.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SearchMessages.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/dialogs/SearchMessages.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGtD,MAAM,WAAW,KAAK;IACpB,uCAAuC;IACvC,MAAM,EAAE,OAAO,CAAC;IAChB,yCAAyC;IACzC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gDAAgD;IAChD,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,qBAAqB;IACrB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,yCAAyC;IACzC,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AAmLD,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* MessageInput - Chat message input bar
|
|
4
4
|
* Text area with send button. Shows reply preview when replying.
|
|
5
5
|
*/
|
|
6
|
+
import { Textarea } from '@happyvertical/smrt-ui/forms';
|
|
6
7
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
7
8
|
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
8
9
|
import { M } from '../../i18n.messages.js';
|
|
@@ -31,7 +32,9 @@ const {
|
|
|
31
32
|
}: Props = $props();
|
|
32
33
|
|
|
33
34
|
let content = $state('');
|
|
34
|
-
|
|
35
|
+
// Captured from the textarea's input event so auto-resize works without binding
|
|
36
|
+
// to the Textarea primitive's inner DOM node (the primitive doesn't expose it).
|
|
37
|
+
let textareaEl: HTMLTextAreaElement | undefined;
|
|
35
38
|
|
|
36
39
|
function handleSend() {
|
|
37
40
|
const trimmed = content.trim();
|
|
@@ -50,10 +53,11 @@ function handleKeydown(event: KeyboardEvent) {
|
|
|
50
53
|
}
|
|
51
54
|
}
|
|
52
55
|
|
|
53
|
-
function handleInput() {
|
|
54
|
-
|
|
55
|
-
textareaEl
|
|
56
|
-
|
|
56
|
+
function handleInput(event: Event) {
|
|
57
|
+
const el = event.currentTarget as HTMLTextAreaElement;
|
|
58
|
+
textareaEl = el;
|
|
59
|
+
el.style.height = 'auto';
|
|
60
|
+
el.style.height = `${Math.min(el.scrollHeight, 120)}px`;
|
|
57
61
|
}
|
|
58
62
|
</script>
|
|
59
63
|
|
|
@@ -80,17 +84,16 @@ function handleInput() {
|
|
|
80
84
|
{/if}
|
|
81
85
|
|
|
82
86
|
<div class="message-input__bar">
|
|
83
|
-
<
|
|
84
|
-
bind:this={textareaEl}
|
|
87
|
+
<Textarea
|
|
85
88
|
bind:value={content}
|
|
86
89
|
class="message-input__textarea"
|
|
87
90
|
{placeholder}
|
|
88
91
|
{disabled}
|
|
89
|
-
rows=
|
|
92
|
+
rows={1}
|
|
90
93
|
onkeydown={handleKeydown}
|
|
91
94
|
oninput={handleInput}
|
|
92
95
|
aria-label={t(M['chat.message_input.input_label'])}
|
|
93
|
-
|
|
96
|
+
/>
|
|
94
97
|
<Button
|
|
95
98
|
variant="ghost"
|
|
96
99
|
class="message-input__send"
|
|
@@ -181,31 +184,17 @@ function handleInput() {
|
|
|
181
184
|
padding: var(--smrt-spacing-3, 0.75rem) var(--smrt-spacing-4, 1rem);
|
|
182
185
|
}
|
|
183
186
|
|
|
184
|
-
|
|
187
|
+
/* Layout/sizing for the Textarea primitive's rendered <textarea>; tokenised
|
|
188
|
+
border/focus/placeholder styling comes from the primitive. The height is
|
|
189
|
+
JS-driven (auto-resize), so resize is disabled and capped at max-height. */
|
|
190
|
+
:global(.message-input__textarea) {
|
|
185
191
|
flex: 1;
|
|
186
192
|
resize: none;
|
|
187
|
-
|
|
188
|
-
border-radius: var(--smrt-radius-medium, 0.5rem);
|
|
189
|
-
padding: var(--smrt-spacing-2, 0.375rem) var(--smrt-spacing-3, 0.75rem);
|
|
190
|
-
font-size: var(--smrt-typography-body-medium-size, 0.875rem);
|
|
191
|
-
font-family: inherit;
|
|
192
|
-
line-height: 1.4;
|
|
193
|
-
background: var(--smrt-color-surface-container-low, #f9fafb);
|
|
194
|
-
color: var(--smrt-color-on-surface, #1b1b1f);
|
|
193
|
+
min-height: auto;
|
|
195
194
|
max-height: 120px;
|
|
196
195
|
overflow-y: auto;
|
|
197
196
|
}
|
|
198
197
|
|
|
199
|
-
.message-input__textarea:focus {
|
|
200
|
-
outline: 2px solid var(--smrt-color-primary, #005ac1);
|
|
201
|
-
outline-offset: -1px;
|
|
202
|
-
border-color: transparent;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.message-input__textarea::placeholder {
|
|
206
|
-
color: var(--smrt-color-outline, #74777f);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
198
|
/* :global() pierces into the Button child's rendered <button> (see #1589). */
|
|
210
199
|
.message-input__bar :global(.message-input__send) {
|
|
211
200
|
width: 36px;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageInput.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/messages/MessageInput.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MessageInput.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/messages/MessageInput.svelte.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,KAAK;IACpB,sCAAsC;IACtC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,OAAO,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACrE,4BAA4B;IAC5B,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AA8ED,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* Displays avatar, message bubble, reactions, reply preview, and tool call data.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { MessageBubble } from '@happyvertical/smrt-ui/chat';
|
|
7
8
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
8
9
|
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
9
10
|
import { M } from '../../i18n.messages.js';
|
|
10
11
|
import type { ChatMessageData } from '../../types.js';
|
|
11
12
|
import Avatar from '../shared/Avatar.svelte';
|
|
12
|
-
import MessageBubble from '../shared/MessageBubble.svelte';
|
|
13
13
|
import ReactionPicker from '../shared/ReactionPicker.svelte';
|
|
14
14
|
|
|
15
15
|
const { t } = useI18n();
|
|
@@ -111,7 +111,7 @@ function handleEscape(event: KeyboardEvent) {
|
|
|
111
111
|
|
|
112
112
|
{#if message.messageType === 'system' || message.messageType === 'action'}
|
|
113
113
|
<div class="message-item message-item--system">
|
|
114
|
-
<MessageBubble content={message.content}
|
|
114
|
+
<MessageBubble content={message.content} own={false} variant="system" />
|
|
115
115
|
</div>
|
|
116
116
|
{:else}
|
|
117
117
|
<div
|
|
@@ -147,7 +147,7 @@ function handleEscape(event: KeyboardEvent) {
|
|
|
147
147
|
|
|
148
148
|
<MessageBubble
|
|
149
149
|
content={message.content}
|
|
150
|
-
{isOwn}
|
|
150
|
+
own={isOwn}
|
|
151
151
|
variant={bubbleVariant}
|
|
152
152
|
/>
|
|
153
153
|
|
|
@@ -533,6 +533,10 @@ function handleEscape(event: KeyboardEvent) {
|
|
|
533
533
|
right: var(--smrt-spacing-4, 1rem);
|
|
534
534
|
z-index: 10;
|
|
535
535
|
transform: translateY(-100%);
|
|
536
|
+
/* The picker primitive supplies its own surface + radius; the popover
|
|
537
|
+
wrapper lifts it off the conversation with matching elevation. */
|
|
538
|
+
border-radius: var(--smrt-radius-large, 0.75rem);
|
|
539
|
+
box-shadow: var(--smrt-elevation-2, 0 2px 6px rgba(0, 0, 0, 0.15));
|
|
536
540
|
}
|
|
537
541
|
|
|
538
542
|
.message-item--own .message-item__picker-popover {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageItem.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/messages/MessageItem.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MessageItem.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/messages/MessageItem.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAKtD,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;AAwMD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -169,6 +169,7 @@ function getFileIcon(type: string): string {
|
|
|
169
169
|
ondragleave={handleDragLeave}
|
|
170
170
|
>
|
|
171
171
|
<label class="file-upload__label">
|
|
172
|
+
<!-- raw-primitive-allow: visually-hidden native file input that backs a custom drag-and-drop zone; the base Input primitive renders a visible bordered control and cannot be the hidden picker behind this drop zone -->
|
|
172
173
|
<input
|
|
173
174
|
type="file"
|
|
174
175
|
class="file-upload__input"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileUpload.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/shared/FileUpload.svelte.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,KAAK;IACpB,uCAAuC;IACvC,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IACpC,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"FileUpload.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/shared/FileUpload.svelte.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,KAAK;IACpB,uCAAuC;IACvC,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IACpC,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA2JD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
/**
|
|
3
|
-
* ReactionPicker
|
|
4
|
-
*
|
|
3
|
+
* ReactionPicker — chat's emoji reaction selector.
|
|
4
|
+
*
|
|
5
|
+
* A thin adapter over the canonical `@happyvertical/smrt-ui/chat`
|
|
6
|
+
* `ReactionPicker` (no duplicated markup/styles): it supplies chat's emoji
|
|
7
|
+
* palette and routes the group + per-emoji labels through chat's i18n catalog,
|
|
8
|
+
* and preserves the package's `onreact` / `isOpen` vocabulary + the
|
|
9
|
+
* `ModuleUIRegistry` registration.
|
|
5
10
|
*/
|
|
11
|
+
import { ReactionPicker as UIReactionPicker } from '@happyvertical/smrt-ui/chat';
|
|
6
12
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
7
|
-
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
8
13
|
import { M } from '../../i18n.js';
|
|
9
14
|
|
|
10
15
|
const { t } = useI18n();
|
|
@@ -36,55 +41,12 @@ const emojis = [
|
|
|
36
41
|
'\u{274C}',
|
|
37
42
|
'\u{1F4AF}',
|
|
38
43
|
];
|
|
39
|
-
|
|
40
|
-
function handleSelect(emoji: string) {
|
|
41
|
-
onreact(emoji);
|
|
42
|
-
}
|
|
43
44
|
</script>
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
type="button"
|
|
53
|
-
onclick={() => handleSelect(emoji)}
|
|
54
|
-
aria-label={t(M['chat.reaction_picker.react_with'], { emoji })}
|
|
55
|
-
>
|
|
56
|
-
{emoji}
|
|
57
|
-
</Button>
|
|
58
|
-
{/each}
|
|
59
|
-
</div>
|
|
60
|
-
{/if}
|
|
61
|
-
|
|
62
|
-
<style>
|
|
63
|
-
.reaction-picker {
|
|
64
|
-
display: grid;
|
|
65
|
-
grid-template-columns: repeat(8, 1fr);
|
|
66
|
-
gap: var(--smrt-spacing-1, 0.25rem);
|
|
67
|
-
padding: var(--smrt-spacing-2, 0.375rem);
|
|
68
|
-
background: var(--smrt-color-surface, #ffffff);
|
|
69
|
-
border: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
|
|
70
|
-
border-radius: var(--smrt-radius-medium, 0.5rem);
|
|
71
|
-
box-shadow: var(--smrt-elevation-2, 0 2px 6px rgba(0, 0, 0, 0.15));
|
|
72
|
-
width: max-content;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/* :global() pierces into each Button's rendered <button> (see #1589). */
|
|
76
|
-
.reaction-picker :global(.reaction-picker__item) {
|
|
77
|
-
width: 32px;
|
|
78
|
-
height: 32px;
|
|
79
|
-
border-radius: var(--smrt-radius-small, 0.25rem);
|
|
80
|
-
background: transparent;
|
|
81
|
-
font-size: var(--smrt-typography-body-large-size, 1.125rem);
|
|
82
|
-
line-height: 1;
|
|
83
|
-
padding: 0;
|
|
84
|
-
transition: background var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
.reaction-picker :global(.reaction-picker__item:hover) {
|
|
88
|
-
background: var(--smrt-color-surface-container-high, #e1e3e8);
|
|
89
|
-
}
|
|
90
|
-
</style>
|
|
46
|
+
<UIReactionPicker
|
|
47
|
+
{emojis}
|
|
48
|
+
{isOpen}
|
|
49
|
+
label={t(M['chat.reaction_picker.reactions'])}
|
|
50
|
+
emojiLabel={(emoji) => t(M['chat.reaction_picker.react_with'], { emoji })}
|
|
51
|
+
onpick={onreact}
|
|
52
|
+
/>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactionPicker.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/shared/ReactionPicker.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ReactionPicker.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/shared/ReactionPicker.svelte.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,KAAK;IACpB,yCAAyC;IACzC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,oCAAoC;IACpC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAoCD,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Simplified message list + input in minimal space.
|
|
5
5
|
* No thread panel, no reactions. Just messages and a send box.
|
|
6
6
|
*/
|
|
7
|
+
import { Form, Input } from '@happyvertical/smrt-ui/forms';
|
|
7
8
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
8
9
|
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
9
10
|
import { M } from '../../i18n.messages.js';
|
|
@@ -86,8 +87,8 @@ $effect(() => {
|
|
|
86
87
|
{/if}
|
|
87
88
|
</div>
|
|
88
89
|
|
|
89
|
-
<
|
|
90
|
-
<
|
|
90
|
+
<Form class="mini-chat__input-bar" onsubmit={handleSubmit}>
|
|
91
|
+
<Input
|
|
91
92
|
class="mini-chat__input"
|
|
92
93
|
type="text"
|
|
93
94
|
placeholder={t(M['chat.mini_chat.placeholder'])}
|
|
@@ -106,7 +107,7 @@ $effect(() => {
|
|
|
106
107
|
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
|
|
107
108
|
</svg>
|
|
108
109
|
</Button>
|
|
109
|
-
</
|
|
110
|
+
</Form>
|
|
110
111
|
</div>
|
|
111
112
|
|
|
112
113
|
<style>
|
|
@@ -185,7 +186,8 @@ $effect(() => {
|
|
|
185
186
|
color: var(--smrt-color-outline, #74777f);
|
|
186
187
|
}
|
|
187
188
|
|
|
188
|
-
.
|
|
189
|
+
/* :global() targets the Form primitive's rendered <form> (see #1589 scoping trap). */
|
|
190
|
+
:global(.mini-chat__input-bar) {
|
|
189
191
|
display: flex;
|
|
190
192
|
align-items: center;
|
|
191
193
|
gap: var(--smrt-spacing-1, 4px);
|
|
@@ -194,28 +196,16 @@ $effect(() => {
|
|
|
194
196
|
background: var(--smrt-color-surface, #fefbff);
|
|
195
197
|
}
|
|
196
198
|
|
|
197
|
-
|
|
199
|
+
/* Layout for the Input primitive's rendered <input> (tokenised styling comes
|
|
200
|
+
from the primitive; this only sizes/shapes it within the input bar). */
|
|
201
|
+
:global(.mini-chat__input) {
|
|
198
202
|
flex: 1;
|
|
199
|
-
border: 1px solid var(--smrt-color-outline-variant, #c4c6d0);
|
|
200
203
|
border-radius: var(--smrt-radius-full, 9999px);
|
|
201
|
-
padding: var(--smrt-spacing-2, 8px) var(--smrt-spacing-3, 12px);
|
|
202
|
-
font: var(--smrt-typography-body-small-font, 0.8125rem/1.4 sans-serif);
|
|
203
|
-
color: var(--smrt-color-on-surface, #1a1c1e);
|
|
204
|
-
background: var(--smrt-color-surface-container-low, #f7f7fb);
|
|
205
|
-
outline: none;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.mini-chat__input:focus {
|
|
209
|
-
border-color: var(--smrt-color-primary, #005ac1);
|
|
210
|
-
box-shadow: 0 0 0 1px var(--smrt-color-primary, #005ac1);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
.mini-chat__input::placeholder {
|
|
214
|
-
color: var(--smrt-color-outline, #74777f);
|
|
215
204
|
}
|
|
216
205
|
|
|
217
|
-
/* :global() pierces into the Button child's rendered <button> (see #1589).
|
|
218
|
-
|
|
206
|
+
/* :global() pierces into the Button child's rendered <button> (see #1589).
|
|
207
|
+
The ancestor is the Form primitive's <form>, also global. */
|
|
208
|
+
:global(.mini-chat__input-bar .mini-chat__send-btn) {
|
|
219
209
|
width: 32px;
|
|
220
210
|
height: 32px;
|
|
221
211
|
padding: 0;
|
|
@@ -226,11 +216,11 @@ $effect(() => {
|
|
|
226
216
|
transition: opacity var(--smrt-duration-short2, 150ms);
|
|
227
217
|
}
|
|
228
218
|
|
|
229
|
-
.mini-chat__input-bar
|
|
219
|
+
:global(.mini-chat__input-bar .mini-chat__send-btn:disabled) {
|
|
230
220
|
opacity: 0.4;
|
|
231
221
|
}
|
|
232
222
|
|
|
233
|
-
.mini-chat__input-bar
|
|
223
|
+
:global(.mini-chat__input-bar .mini-chat__send-btn:not(:disabled):hover) {
|
|
234
224
|
background: var(--smrt-color-primary, #005ac1);
|
|
235
225
|
opacity: 0.85;
|
|
236
226
|
}
|
|
@@ -240,7 +230,7 @@ $effect(() => {
|
|
|
240
230
|
scroll-behavior: auto;
|
|
241
231
|
}
|
|
242
232
|
|
|
243
|
-
.mini-chat__input-bar
|
|
233
|
+
:global(.mini-chat__input-bar .mini-chat__send-btn) {
|
|
244
234
|
transition: none;
|
|
245
235
|
}
|
|
246
236
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MiniChat.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/tabs/MiniChat.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MiniChat.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/tabs/MiniChat.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAItD,MAAM,WAAW,KAAK;IACpB,0BAA0B;IAC1B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,gCAAgC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IACzB,qBAAqB;IACrB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAoFD,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
package/dist/svelte/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* const Component = ModuleUIRegistry.get('@happyvertical/smrt-chat', 'chat-layout');
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
|
+
export { MessageBubble, TypingIndicator } from '@happyvertical/smrt-ui/chat';
|
|
20
21
|
export { default as AgentChat } from './components/agent/AgentChat.svelte';
|
|
21
22
|
export { default as AgentSelector } from './components/agent/AgentSelector.svelte';
|
|
22
23
|
export { default as AgentSessionPanel } from './components/agent/AgentSessionPanel.svelte';
|
|
@@ -35,10 +36,8 @@ export { default as Avatar } from './components/shared/Avatar.svelte';
|
|
|
35
36
|
export { default as FileUpload } from './components/shared/FileUpload.svelte';
|
|
36
37
|
export { default as LinkPreview } from './components/shared/LinkPreview.svelte';
|
|
37
38
|
export { default as MentionAutocomplete } from './components/shared/MentionAutocomplete.svelte';
|
|
38
|
-
export { default as MessageBubble } from './components/shared/MessageBubble.svelte';
|
|
39
39
|
export { default as ReactionPicker } from './components/shared/ReactionPicker.svelte';
|
|
40
40
|
export { default as ReadReceipts } from './components/shared/ReadReceipts.svelte';
|
|
41
|
-
export { default as TypingIndicator } from './components/shared/TypingIndicator.svelte';
|
|
42
41
|
export { default as UserPresence } from './components/shared/UserPresence.svelte';
|
|
43
42
|
export { default as ChatTab } from './components/tabs/ChatTab.svelte';
|
|
44
43
|
export { default as ChatTabList } from './components/tabs/ChatTabList.svelte';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAwCH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,qCAAqC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,yCAAyC,CAAC;AACnF,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,6CAA6C,CAAC;AAC3F,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,2CAA2C,CAAC;AACvF,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AAC3F,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,4CAA4C,CAAC;AAEvF,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,2CAA2C,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,0CAA0C,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,0CAA0C,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,0CAA0C,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,gDAAgD,CAAC;AAChG,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAGxE,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,eAAe,EACf,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
|
package/dist/svelte/index.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* const Component = ModuleUIRegistry.get('@happyvertical/smrt-chat', 'chat-layout');
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
|
+
import { MessageBubble, TypingIndicator } from '@happyvertical/smrt-ui/chat';
|
|
20
21
|
import { ModuleUIRegistry } from '@happyvertical/smrt-ui/registry';
|
|
21
22
|
import { CHAT_MODULE_META } from '../ui.js';
|
|
22
23
|
// Agent components
|
|
@@ -42,16 +43,18 @@ import Avatar from './components/shared/Avatar.svelte';
|
|
|
42
43
|
import FileUpload from './components/shared/FileUpload.svelte';
|
|
43
44
|
import LinkPreview from './components/shared/LinkPreview.svelte';
|
|
44
45
|
import MentionAutocomplete from './components/shared/MentionAutocomplete.svelte';
|
|
45
|
-
import MessageBubble from './components/shared/MessageBubble.svelte';
|
|
46
46
|
import ReactionPicker from './components/shared/ReactionPicker.svelte';
|
|
47
47
|
import ReadReceipts from './components/shared/ReadReceipts.svelte';
|
|
48
|
-
import TypingIndicator from './components/shared/TypingIndicator.svelte';
|
|
49
48
|
import UserPresence from './components/shared/UserPresence.svelte';
|
|
50
49
|
// Tab components
|
|
51
50
|
import ChatTab from './components/tabs/ChatTab.svelte';
|
|
52
51
|
import ChatTabList from './components/tabs/ChatTabList.svelte';
|
|
53
52
|
import ChatTabs from './components/tabs/ChatTabs.svelte';
|
|
54
53
|
import MiniChat from './components/tabs/MiniChat.svelte';
|
|
54
|
+
// MessageBubble + TypingIndicator are re-exported from the canonical
|
|
55
|
+
// `@happyvertical/smrt-ui/chat` primitives (consolidated in #1589);
|
|
56
|
+
// ReactionPicker keeps a thin chat-local adapter for the package's i18n + palette.
|
|
57
|
+
export { MessageBubble, TypingIndicator } from '@happyvertical/smrt-ui/chat';
|
|
55
58
|
export { default as AgentChat } from './components/agent/AgentChat.svelte';
|
|
56
59
|
export { default as AgentSelector } from './components/agent/AgentSelector.svelte';
|
|
57
60
|
export { default as AgentSessionPanel } from './components/agent/AgentSessionPanel.svelte';
|
|
@@ -71,10 +74,8 @@ export { default as Avatar } from './components/shared/Avatar.svelte';
|
|
|
71
74
|
export { default as FileUpload } from './components/shared/FileUpload.svelte';
|
|
72
75
|
export { default as LinkPreview } from './components/shared/LinkPreview.svelte';
|
|
73
76
|
export { default as MentionAutocomplete } from './components/shared/MentionAutocomplete.svelte';
|
|
74
|
-
export { default as MessageBubble } from './components/shared/MessageBubble.svelte';
|
|
75
77
|
export { default as ReactionPicker } from './components/shared/ReactionPicker.svelte';
|
|
76
78
|
export { default as ReadReceipts } from './components/shared/ReadReceipts.svelte';
|
|
77
|
-
export { default as TypingIndicator } from './components/shared/TypingIndicator.svelte';
|
|
78
79
|
export { default as UserPresence } from './components/shared/UserPresence.svelte';
|
|
79
80
|
export { default as ChatTab } from './components/tabs/ChatTab.svelte';
|
|
80
81
|
export { default as ChatTabList } from './components/tabs/ChatTabList.svelte';
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-chat",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.7",
|
|
4
4
|
"description": "Chat rooms, DMs, threads, and agent conversations for the SMRT framework",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"smrtRawPrimitives": "strict
|
|
6
|
+
"smrtRawPrimitives": "strict",
|
|
7
7
|
"main": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
@@ -56,10 +56,10 @@
|
|
|
56
56
|
"access": "public"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@happyvertical/smrt-
|
|
60
|
-
"@happyvertical/smrt-
|
|
61
|
-
"@happyvertical/smrt-types": "0.34.
|
|
62
|
-
"@happyvertical/smrt-ui": "0.34.
|
|
59
|
+
"@happyvertical/smrt-tenancy": "0.34.7",
|
|
60
|
+
"@happyvertical/smrt-core": "0.34.7",
|
|
61
|
+
"@happyvertical/smrt-types": "0.34.7",
|
|
62
|
+
"@happyvertical/smrt-ui": "0.34.7"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"svelte": "^5.18.0"
|
|
@@ -79,9 +79,9 @@
|
|
|
79
79
|
"typescript": "^5.9.3",
|
|
80
80
|
"vite": "^7.3.1",
|
|
81
81
|
"vitest": "^4.0.17",
|
|
82
|
-
"@happyvertical/smrt-agents": "0.34.
|
|
83
|
-
"@happyvertical/smrt-profiles": "0.34.
|
|
84
|
-
"@happyvertical/smrt-vitest": "0.34.
|
|
82
|
+
"@happyvertical/smrt-agents": "0.34.7",
|
|
83
|
+
"@happyvertical/smrt-profiles": "0.34.7",
|
|
84
|
+
"@happyvertical/smrt-vitest": "0.34.7"
|
|
85
85
|
},
|
|
86
86
|
"scripts": {
|
|
87
87
|
"build": "vite build --mode library && svelte-package -i src/svelte -o dist/svelte --tsconfig tsconfig.svelte.json",
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* MessageBubble - Styled message bubble
|
|
4
|
-
* Different styling for own messages, others' messages, agent, and system.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export interface Props {
|
|
8
|
-
/** Message content text */
|
|
9
|
-
content: string;
|
|
10
|
-
/** Whether this message was sent by the current user */
|
|
11
|
-
isOwn: boolean;
|
|
12
|
-
/** Visual variant of the bubble */
|
|
13
|
-
variant?: 'default' | 'agent' | 'system';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const { content, isOwn, variant = 'default' }: Props = $props();
|
|
17
|
-
</script>
|
|
18
|
-
|
|
19
|
-
<div
|
|
20
|
-
class="bubble {variant}"
|
|
21
|
-
class:own={isOwn}
|
|
22
|
-
>
|
|
23
|
-
<p class="bubble__content">{content}</p>
|
|
24
|
-
</div>
|
|
25
|
-
|
|
26
|
-
<style>
|
|
27
|
-
.bubble {
|
|
28
|
-
max-width: 75%;
|
|
29
|
-
padding: var(--smrt-spacing-3, 0.75rem) var(--smrt-spacing-4, 1rem);
|
|
30
|
-
border-radius: var(--smrt-radius-large, 1rem);
|
|
31
|
-
word-break: break-word;
|
|
32
|
-
line-height: 1.4;
|
|
33
|
-
font-size: var(--smrt-typography-body-medium-size, 0.875rem);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.bubble__content {
|
|
37
|
-
margin: 0;
|
|
38
|
-
white-space: pre-wrap;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/* Default variant - received */
|
|
42
|
-
.default {
|
|
43
|
-
background: var(--smrt-color-surface-container, #f3f4f6);
|
|
44
|
-
color: var(--smrt-color-on-surface, #1b1b1f);
|
|
45
|
-
border-bottom-left-radius: var(--smrt-radius-small, 0.25rem);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/* Default variant - own message */
|
|
49
|
-
.default.own {
|
|
50
|
-
background: var(--smrt-color-primary, #005ac1);
|
|
51
|
-
color: var(--smrt-color-on-primary, #ffffff);
|
|
52
|
-
border-bottom-right-radius: var(--smrt-radius-small, 0.25rem);
|
|
53
|
-
border-bottom-left-radius: var(--smrt-radius-large, 1rem);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/* Agent variant */
|
|
57
|
-
.agent {
|
|
58
|
-
background: var(--smrt-color-tertiary-container, #ffd8e4);
|
|
59
|
-
color: var(--smrt-color-on-tertiary-container, #31111d);
|
|
60
|
-
border-bottom-left-radius: var(--smrt-radius-small, 0.25rem);
|
|
61
|
-
border-left: 3px solid var(--smrt-color-tertiary, #7d5260);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.agent.own {
|
|
65
|
-
border-left: none;
|
|
66
|
-
border-right: 3px solid var(--smrt-color-tertiary, #7d5260);
|
|
67
|
-
border-bottom-right-radius: var(--smrt-radius-small, 0.25rem);
|
|
68
|
-
border-bottom-left-radius: var(--smrt-radius-large, 1rem);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/* System variant */
|
|
72
|
-
.system {
|
|
73
|
-
max-width: 100%;
|
|
74
|
-
background: transparent;
|
|
75
|
-
color: var(--smrt-color-on-surface-variant, #43474e);
|
|
76
|
-
text-align: center;
|
|
77
|
-
font-size: var(--smrt-typography-body-small-size, 0.75rem);
|
|
78
|
-
padding: var(--smrt-spacing-2, 0.375rem) var(--smrt-spacing-4, 1rem);
|
|
79
|
-
border-radius: 0;
|
|
80
|
-
}
|
|
81
|
-
</style>
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MessageBubble - Styled message bubble
|
|
3
|
-
* Different styling for own messages, others' messages, agent, and system.
|
|
4
|
-
*/
|
|
5
|
-
export interface Props {
|
|
6
|
-
/** Message content text */
|
|
7
|
-
content: string;
|
|
8
|
-
/** Whether this message was sent by the current user */
|
|
9
|
-
isOwn: boolean;
|
|
10
|
-
/** Visual variant of the bubble */
|
|
11
|
-
variant?: 'default' | 'agent' | 'system';
|
|
12
|
-
}
|
|
13
|
-
declare const MessageBubble: import("svelte").Component<Props, {}, "">;
|
|
14
|
-
type MessageBubble = ReturnType<typeof MessageBubble>;
|
|
15
|
-
export default MessageBubble;
|
|
16
|
-
//# sourceMappingURL=MessageBubble.svelte.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MessageBubble.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/shared/MessageBubble.svelte.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,MAAM,WAAW,KAAK;IACpB,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,KAAK,EAAE,OAAO,CAAC;IACf,mCAAmC;IACnC,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;CAC1C;AAeD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* TypingIndicator - Animated "typing..." display
|
|
4
|
-
* Shows "Alice is typing..." or "Alice and Bob are typing..." with animated dots.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export interface Props {
|
|
8
|
-
/** Names of users currently typing */
|
|
9
|
-
names: string[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const { names }: Props = $props();
|
|
13
|
-
|
|
14
|
-
const label = $derived.by(() => {
|
|
15
|
-
if (names.length === 0) return '';
|
|
16
|
-
if (names.length === 1) return `${names[0]} is typing`;
|
|
17
|
-
if (names.length === 2) return `${names[0]} and ${names[1]} are typing`;
|
|
18
|
-
return `${names[0]} and ${names.length - 1} others are typing`;
|
|
19
|
-
});
|
|
20
|
-
</script>
|
|
21
|
-
|
|
22
|
-
{#if names.length > 0}
|
|
23
|
-
<div class="typing" aria-live="polite" aria-label="{label}">
|
|
24
|
-
<span class="typing__text">{label}</span>
|
|
25
|
-
<span class="typing__dots" aria-hidden="true">
|
|
26
|
-
<span class="typing__dot"></span>
|
|
27
|
-
<span class="typing__dot"></span>
|
|
28
|
-
<span class="typing__dot"></span>
|
|
29
|
-
</span>
|
|
30
|
-
</div>
|
|
31
|
-
{/if}
|
|
32
|
-
|
|
33
|
-
<style>
|
|
34
|
-
.typing {
|
|
35
|
-
display: inline-flex;
|
|
36
|
-
align-items: center;
|
|
37
|
-
gap: var(--smrt-spacing-1, 0.25rem);
|
|
38
|
-
padding: var(--smrt-spacing-1, 0.25rem) var(--smrt-spacing-3, 0.75rem);
|
|
39
|
-
font-size: var(--smrt-typography-body-small-size, 0.75rem);
|
|
40
|
-
color: var(--smrt-color-on-surface-variant, #43474e);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.typing__text {
|
|
44
|
-
white-space: nowrap;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.typing__dots {
|
|
48
|
-
display: inline-flex;
|
|
49
|
-
align-items: center;
|
|
50
|
-
gap: var(--smrt-spacing-1, 4px);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.typing__dot {
|
|
54
|
-
width: 4px;
|
|
55
|
-
height: 4px;
|
|
56
|
-
border-radius: var(--smrt-radius-full, 9999px);
|
|
57
|
-
background: var(--smrt-color-on-surface-variant, #43474e);
|
|
58
|
-
animation: typing-bounce 1.4s infinite ease-in-out both;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.typing__dot:nth-child(1) {
|
|
62
|
-
animation-delay: 0s;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.typing__dot:nth-child(2) {
|
|
66
|
-
animation-delay: 0.2s;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.typing__dot:nth-child(3) {
|
|
70
|
-
animation-delay: 0.4s;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
@keyframes typing-bounce {
|
|
74
|
-
0%, 80%, 100% {
|
|
75
|
-
opacity: 0.3;
|
|
76
|
-
transform: scale(0.8);
|
|
77
|
-
}
|
|
78
|
-
40% {
|
|
79
|
-
opacity: 1;
|
|
80
|
-
transform: scale(1);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
@media (prefers-reduced-motion: reduce) {
|
|
85
|
-
.typing__dot {
|
|
86
|
-
animation: none;
|
|
87
|
-
opacity: 0.6;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
</style>
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TypingIndicator - Animated "typing..." display
|
|
3
|
-
* Shows "Alice is typing..." or "Alice and Bob are typing..." with animated dots.
|
|
4
|
-
*/
|
|
5
|
-
export interface Props {
|
|
6
|
-
/** Names of users currently typing */
|
|
7
|
-
names: string[];
|
|
8
|
-
}
|
|
9
|
-
declare const TypingIndicator: import("svelte").Component<Props, {}, "">;
|
|
10
|
-
type TypingIndicator = ReturnType<typeof TypingIndicator>;
|
|
11
|
-
export default TypingIndicator;
|
|
12
|
-
//# sourceMappingURL=TypingIndicator.svelte.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"TypingIndicator.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/shared/TypingIndicator.svelte.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,MAAM,WAAW,KAAK;IACpB,sCAAsC;IACtC,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AA6BD,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
/**
|
|
3
|
-
* Component coverage for MessageBubble via the shared S11 harness (#1416).
|
|
4
|
-
*/
|
|
5
|
-
import { expectNoA11yViolations, render, screen, } from '@happyvertical/smrt-vitest/svelte';
|
|
6
|
-
import { describe, expect, it } from 'vitest';
|
|
7
|
-
import MessageBubble from '../MessageBubble.svelte';
|
|
8
|
-
describe('MessageBubble', () => {
|
|
9
|
-
it('renders the message content', () => {
|
|
10
|
-
render(MessageBubble, {
|
|
11
|
-
props: { content: 'Hello there', isOwn: false },
|
|
12
|
-
});
|
|
13
|
-
expect(screen.getByText('Hello there')).toBeInTheDocument();
|
|
14
|
-
});
|
|
15
|
-
it('is axe-clean for an own message', async () => {
|
|
16
|
-
const { container } = render(MessageBubble, {
|
|
17
|
-
props: { content: 'Hi', isOwn: true },
|
|
18
|
-
});
|
|
19
|
-
await expectNoA11yViolations(container);
|
|
20
|
-
});
|
|
21
|
-
});
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
/**
|
|
3
|
-
* Component coverage for TypingIndicator via the shared S11 harness (#1416).
|
|
4
|
-
*/
|
|
5
|
-
import { expectNoA11yViolations, render, screen, } from '@happyvertical/smrt-vitest/svelte';
|
|
6
|
-
import { describe, expect, it } from 'vitest';
|
|
7
|
-
import TypingIndicator from '../TypingIndicator.svelte';
|
|
8
|
-
describe('TypingIndicator', () => {
|
|
9
|
-
it('names a single typist', () => {
|
|
10
|
-
render(TypingIndicator, { props: { names: ['Ada'] } });
|
|
11
|
-
expect(screen.getByText('Ada is typing')).toBeInTheDocument();
|
|
12
|
-
});
|
|
13
|
-
it('names two typists', () => {
|
|
14
|
-
render(TypingIndicator, { props: { names: ['Ada', 'Bob'] } });
|
|
15
|
-
expect(screen.getByText('Ada and Bob are typing')).toBeInTheDocument();
|
|
16
|
-
});
|
|
17
|
-
it('renders nothing when nobody is typing', () => {
|
|
18
|
-
const { container } = render(TypingIndicator, { props: { names: [] } });
|
|
19
|
-
expect(container.querySelector('.typing')).toBeNull();
|
|
20
|
-
});
|
|
21
|
-
it('is axe-clean', async () => {
|
|
22
|
-
const { container } = render(TypingIndicator, {
|
|
23
|
-
props: { names: ['Ada'] },
|
|
24
|
-
});
|
|
25
|
-
await expectNoA11yViolations(container);
|
|
26
|
-
});
|
|
27
|
-
});
|