@happyvertical/smrt-chat 0.34.6 → 0.34.8

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.
Files changed (30) hide show
  1. package/dist/manifest.json +2 -2
  2. package/dist/smrt-knowledge.json +4 -4
  3. package/dist/svelte/components/agent/AgentChat.svelte +14 -26
  4. package/dist/svelte/components/agent/AgentChat.svelte.d.ts.map +1 -1
  5. package/dist/svelte/components/dialogs/RoomCreateDialog.svelte +20 -50
  6. package/dist/svelte/components/dialogs/RoomCreateDialog.svelte.d.ts.map +1 -1
  7. package/dist/svelte/components/dialogs/SearchMessages.svelte +28 -12
  8. package/dist/svelte/components/dialogs/SearchMessages.svelte.d.ts.map +1 -1
  9. package/dist/svelte/components/messages/MessageInput.svelte +17 -28
  10. package/dist/svelte/components/messages/MessageInput.svelte.d.ts.map +1 -1
  11. package/dist/svelte/components/messages/MessageItem.svelte +7 -3
  12. package/dist/svelte/components/messages/MessageItem.svelte.d.ts.map +1 -1
  13. package/dist/svelte/components/shared/FileUpload.svelte +1 -0
  14. package/dist/svelte/components/shared/FileUpload.svelte.d.ts.map +1 -1
  15. package/dist/svelte/components/shared/ReactionPicker.svelte +15 -53
  16. package/dist/svelte/components/shared/ReactionPicker.svelte.d.ts.map +1 -1
  17. package/dist/svelte/components/tabs/MiniChat.svelte +15 -25
  18. package/dist/svelte/components/tabs/MiniChat.svelte.d.ts.map +1 -1
  19. package/dist/svelte/index.d.ts +1 -2
  20. package/dist/svelte/index.d.ts.map +1 -1
  21. package/dist/svelte/index.js +5 -4
  22. package/package.json +9 -9
  23. package/dist/svelte/components/shared/MessageBubble.svelte +0 -81
  24. package/dist/svelte/components/shared/MessageBubble.svelte.d.ts +0 -16
  25. package/dist/svelte/components/shared/MessageBubble.svelte.d.ts.map +0 -1
  26. package/dist/svelte/components/shared/TypingIndicator.svelte +0 -90
  27. package/dist/svelte/components/shared/TypingIndicator.svelte.d.ts +0 -12
  28. package/dist/svelte/components/shared/TypingIndicator.svelte.d.ts.map +0 -1
  29. package/dist/svelte/components/shared/__tests__/MessageBubble.test.js +0 -21
  30. package/dist/svelte/components/shared/__tests__/TypingIndicator.test.js +0 -27
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "timestamp": 1782278483770,
3
+ "timestamp": 1782318295117,
4
4
  "packageName": "@happyvertical/smrt-chat",
5
- "packageVersion": "0.34.6",
5
+ "packageVersion": "0.34.8",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-chat:AgentSessionCollection": {
8
8
  "name": "agentsessioncollection",
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-06-24T05:21:24.161Z",
3
+ "generatedAt": "2026-06-24T16:24:55.462Z",
4
4
  "packageName": "@happyvertical/smrt-chat",
5
- "packageVersion": "0.34.6",
5
+ "packageVersion": "0.34.8",
6
6
  "sourceManifestPath": "dist/manifest.json",
7
7
  "agentDocPath": "AGENTS.md",
8
8
  "sourceHashes": {
9
- "manifest": "2975e8cc9d58e5960d1d59de3dc09523b44a85d6af43e7fbef3e9c03f695b2d9",
10
- "packageJson": "5fbeb77ade984295729f043140c7db7b16609c4c5ef9333f04af4df283a14f15",
9
+ "manifest": "5544f0d5ac8ccff3abbeccbf63de82dc9b8fd9bf25e3b8336501144d08558e72",
10
+ "packageJson": "c3bb2f0cc08c04a4f72c9b60c13e9c7aa18ba3e49c116917c4c1e44452545227",
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
- <form class="agent-chat__input-bar" onsubmit={(e) => handleSubmit(e)}>
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
- <textarea
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
- ></textarea>
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
- </form>
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
- .agent-chat__input-bar {
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
- .agent-chat__input {
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
- .agent-chat__input:focus {
348
- outline: none;
349
- background: var(--smrt-color-surface-variant, #e1e2ec);
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 :global(.agent-chat__send-btn:not(:disabled):hover) {
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":"AAWA,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;AAyKD,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
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 && nameInput) {
87
- nameInput.focus();
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
- <form
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
- <input
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="100"
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
- <textarea
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="3"
151
- maxlength="500"
152
- ></textarea>
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
- </form>
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
- .dialog__form {
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":"AAiBA,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;AA0HD,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
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 && searchInput) {
99
- searchInput.focus();
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
- <form
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
- <input
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 = ''; searchInput?.focus(); }}
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
- </form>
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
- .search-form {
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::placeholder {
298
- color: var(--smrt-color-on-surface-variant, #43474e);
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":"AAUA,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;AA2KD,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
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
- let textareaEl: HTMLTextAreaElement | undefined = $state();
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
- if (!textareaEl) return;
55
- textareaEl.style.height = 'auto';
56
- textareaEl.style.height = `${Math.min(textareaEl.scrollHeight, 120)}px`;
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
- <textarea
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="1"
92
+ rows={1}
90
93
  onkeydown={handleKeydown}
91
94
  oninput={handleInput}
92
95
  aria-label={t(M['chat.message_input.input_label'])}
93
- ></textarea>
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
- .message-input__textarea {
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
- border: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
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":"AAYA,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;AA0ED,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
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} isOwn={false} variant="system" />
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":"AAUA,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;AAwMD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
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;AA0JD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
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 - Emoji reaction selector
4
- * Compact popup grid of common emojis.
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
- {#if isOpen}
46
- <div class="reaction-picker" role="group" aria-label={t(M['chat.reaction_picker.reactions'])}>
47
- {#each emojis as emoji}
48
- <Button
49
- variant="ghost"
50
- size="sm"
51
- class="reaction-picker__item"
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":"AAYA,MAAM,WAAW,KAAK;IACpB,yCAAyC;IACzC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,oCAAoC;IACpC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAkDD,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
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
- <form class="mini-chat__input-bar" onsubmit={handleSubmit}>
90
- <input
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
- </form>
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
- .mini-chat__input-bar {
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
- .mini-chat__input {
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
- .mini-chat__input-bar :global(.mini-chat__send-btn) {
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 :global(.mini-chat__send-btn:disabled) {
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 :global(.mini-chat__send-btn:not(:disabled):hover) {
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 :global(.mini-chat__send-btn) {
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":"AAWA,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;AAmFD,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
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"}
@@ -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;AAsCH,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,aAAa,EAAE,MAAM,0CAA0C,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,4CAA4C,CAAC;AACxF,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"}
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"}
@@ -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.6",
3
+ "version": "0.34.8",
4
4
  "description": "Chat rooms, DMs, threads, and agent conversations for the SMRT framework",
5
5
  "type": "module",
6
- "smrtRawPrimitives": "strict-buttons",
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-core": "0.34.6",
60
- "@happyvertical/smrt-tenancy": "0.34.6",
61
- "@happyvertical/smrt-types": "0.34.6",
62
- "@happyvertical/smrt-ui": "0.34.6"
59
+ "@happyvertical/smrt-core": "0.34.8",
60
+ "@happyvertical/smrt-tenancy": "0.34.8",
61
+ "@happyvertical/smrt-types": "0.34.8",
62
+ "@happyvertical/smrt-ui": "0.34.8"
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.6",
83
- "@happyvertical/smrt-profiles": "0.34.6",
84
- "@happyvertical/smrt-vitest": "0.34.6"
82
+ "@happyvertical/smrt-agents": "0.34.8",
83
+ "@happyvertical/smrt-profiles": "0.34.8",
84
+ "@happyvertical/smrt-vitest": "0.34.8"
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
- });