@aleph-alpha/chat-kit 2.2.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/{CkInput.vue_vue_type_script_setup_true_lang-CjfKRuZs.js → CkInput.vue_vue_type_script_setup_true_lang-CwH5M5WD.js} +263 -142
  2. package/dist/chat-kit.css +33 -6
  3. package/dist/components/base/CkDropZoneOverlay/CkDropZoneOverlay.vue.d.ts.map +1 -1
  4. package/dist/components/base/CkFileItemsList/CkFileItemsList.stories.d.ts +7 -0
  5. package/dist/components/base/CkFileItemsList/CkFileItemsList.stories.d.ts.map +1 -1
  6. package/dist/components/base/CkFileItemsList/CkFileItemsList.vue.d.ts +1 -0
  7. package/dist/components/base/CkFileItemsList/CkFileItemsList.vue.d.ts.map +1 -1
  8. package/dist/components/base/CkFileItemsList/types.d.ts +9 -0
  9. package/dist/components/base/CkFileItemsList/types.d.ts.map +1 -1
  10. package/dist/components/base/CkModelSelector/types.d.ts +1 -1
  11. package/dist/components/base/CkPromptInput/CkPromptInput.stories.d.ts +48 -5
  12. package/dist/components/base/CkPromptInput/CkPromptInput.stories.d.ts.map +1 -1
  13. package/dist/components/base/CkPromptInput/CkPromptInput.vue.d.ts +7 -6
  14. package/dist/components/base/CkPromptInput/CkPromptInput.vue.d.ts.map +1 -1
  15. package/dist/components/base/CkPromptInput/types.d.ts +11 -12
  16. package/dist/components/base/CkPromptInput/types.d.ts.map +1 -1
  17. package/dist/components/base/CkPromptMenuButton/CkPromptMenuButton.stories.d.ts +18 -0
  18. package/dist/components/base/CkPromptMenuButton/CkPromptMenuButton.stories.d.ts.map +1 -0
  19. package/dist/components/base/CkPromptMenuButton/CkPromptMenuButton.vue.d.ts +18 -0
  20. package/dist/components/base/CkPromptMenuButton/CkPromptMenuButton.vue.d.ts.map +1 -0
  21. package/dist/components/base/CkPromptMenuButton/index.d.ts +3 -0
  22. package/dist/components/base/CkPromptMenuButton/index.d.ts.map +1 -0
  23. package/dist/components/base/CkPromptMenuButton/types.d.ts +30 -0
  24. package/dist/components/base/CkPromptMenuButton/types.d.ts.map +1 -0
  25. package/dist/components/base/{CkPromptActionButton/CkPromptActionButton.stories.d.ts → CkPromptSendButton/CkPromptSendButton.stories.d.ts} +5 -5
  26. package/dist/components/base/CkPromptSendButton/CkPromptSendButton.stories.d.ts.map +1 -0
  27. package/dist/components/base/{CkPromptActionButton/CkPromptActionButton.vue.d.ts → CkPromptSendButton/CkPromptSendButton.vue.d.ts} +3 -3
  28. package/dist/components/base/CkPromptSendButton/CkPromptSendButton.vue.d.ts.map +1 -0
  29. package/dist/components/base/CkPromptSendButton/index.d.ts +3 -0
  30. package/dist/components/base/CkPromptSendButton/index.d.ts.map +1 -0
  31. package/dist/components/base/CkPromptSendButton/types.d.ts +34 -0
  32. package/dist/components/base/CkPromptSendButton/types.d.ts.map +1 -0
  33. package/dist/components/base/CkPromptStopButton/CkPromptStopButton.stories.d.ts +1 -1
  34. package/dist/components/base/CkPromptStopButton/CkPromptStopButton.vue.d.ts.map +1 -1
  35. package/dist/components/base/CkPromptStopButton/types.d.ts +6 -4
  36. package/dist/components/base/CkPromptStopButton/types.d.ts.map +1 -1
  37. package/dist/components/base/icons/CkSendIcon.vue.d.ts.map +1 -1
  38. package/dist/components/base/index.d.ts +3 -1
  39. package/dist/components/base/index.d.ts.map +1 -1
  40. package/dist/components/composed/CkHistory/CkHistory.vue.d.ts +1 -1
  41. package/dist/components/composed/CkInput/CkInput.stories.d.ts.map +1 -1
  42. package/dist/components/composed/CkInput/CkInput.vue.d.ts +9 -8
  43. package/dist/components/composed/CkInput/CkInput.vue.d.ts.map +1 -1
  44. package/dist/components/composed/CkInput/types.d.ts +2 -7
  45. package/dist/components/composed/CkInput/types.d.ts.map +1 -1
  46. package/dist/components/index.d.ts +2 -2
  47. package/dist/components/index.d.ts.map +1 -1
  48. package/dist/components/index.js +9 -8
  49. package/dist/composables/fileUpload/types.d.ts +1 -1
  50. package/dist/composables/fileUpload/types.d.ts.map +1 -1
  51. package/dist/composables/fileUpload/useFileUpload.d.ts.map +1 -1
  52. package/dist/composables/index.js +1 -1
  53. package/dist/{index-CfXEGjc7.js → index-THzZwfMU.js} +6 -4
  54. package/dist/index.d.ts +2 -2
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +11 -10
  57. package/package.json +1 -1
  58. package/src/components/base/CkDropZoneOverlay/CkDropZoneOverlay.vue +2 -7
  59. package/src/components/base/CkFileItemsList/CkFileItemsList.spec.ts +67 -0
  60. package/src/components/base/CkFileItemsList/CkFileItemsList.stories.ts +48 -0
  61. package/src/components/base/CkFileItemsList/CkFileItemsList.vue +78 -4
  62. package/src/components/base/CkFileItemsList/types.ts +9 -0
  63. package/src/components/base/CkModelSelector/types.ts +1 -1
  64. package/src/components/base/CkPromptInput/CkPromptInput.spec.ts +206 -11
  65. package/src/components/base/CkPromptInput/CkPromptInput.stories.ts +1068 -43
  66. package/src/components/base/CkPromptInput/CkPromptInput.vue +137 -28
  67. package/src/components/base/CkPromptInput/types.ts +11 -12
  68. package/src/components/base/CkPromptMenuButton/CkPromptMenuButton.spec.ts +62 -0
  69. package/src/components/base/CkPromptMenuButton/CkPromptMenuButton.stories.ts +75 -0
  70. package/src/components/base/CkPromptMenuButton/CkPromptMenuButton.vue +26 -0
  71. package/src/components/base/CkPromptMenuButton/index.ts +2 -0
  72. package/src/components/base/CkPromptMenuButton/types.ts +29 -0
  73. package/src/components/base/{CkPromptActionButton/CkPromptActionButton.spec.ts → CkPromptSendButton/CkPromptSendButton.spec.ts} +11 -11
  74. package/src/components/base/CkPromptSendButton/CkPromptSendButton.stories.ts +117 -0
  75. package/src/components/base/{CkPromptActionButton/CkPromptActionButton.vue → CkPromptSendButton/CkPromptSendButton.vue} +4 -4
  76. package/src/components/base/CkPromptSendButton/index.ts +2 -0
  77. package/src/components/base/CkPromptSendButton/types.ts +33 -0
  78. package/src/components/base/CkPromptStopButton/CkPromptStopButton.spec.ts +5 -5
  79. package/src/components/base/CkPromptStopButton/CkPromptStopButton.stories.ts +1 -1
  80. package/src/components/base/CkPromptStopButton/CkPromptStopButton.vue +3 -2
  81. package/src/components/base/CkPromptStopButton/types.ts +6 -4
  82. package/src/components/base/icons/CkSendIcon.vue +3 -16
  83. package/src/components/base/icons/CkStopIcon.vue +1 -1
  84. package/src/components/base/index.ts +3 -1
  85. package/src/components/composed/CkInput/CkInput.stories.ts +0 -4
  86. package/src/components/composed/CkInput/CkInput.vue +7 -5
  87. package/src/components/composed/CkInput/types.ts +2 -7
  88. package/src/components/index.ts +3 -1
  89. package/src/composables/fileUpload/types.ts +1 -1
  90. package/src/composables/fileUpload/useFileUpload.spec.ts +26 -0
  91. package/src/composables/fileUpload/useFileUpload.ts +12 -3
  92. package/src/composables/fileUpload/useFileUploadWrapper.ts +1 -1
  93. package/src/index.ts +3 -1
  94. package/dist/components/base/CkPromptActionButton/CkPromptActionButton.stories.d.ts.map +0 -1
  95. package/dist/components/base/CkPromptActionButton/CkPromptActionButton.vue.d.ts.map +0 -1
  96. package/dist/components/base/CkPromptActionButton/index.d.ts +0 -3
  97. package/dist/components/base/CkPromptActionButton/index.d.ts.map +0 -1
  98. package/dist/components/base/CkPromptActionButton/types.d.ts +0 -33
  99. package/dist/components/base/CkPromptActionButton/types.d.ts.map +0 -1
  100. package/src/components/base/CkPromptActionButton/CkPromptActionButton.stories.ts +0 -126
  101. package/src/components/base/CkPromptActionButton/index.ts +0 -2
  102. package/src/components/base/CkPromptActionButton/types.ts +0 -32
@@ -1,5 +1,29 @@
1
1
  import CkPromptInput from './CkPromptInput.vue';
2
2
  import { render, screen, fireEvent } from '@testing-library/vue';
3
+ import { defineComponent, nextTick, ref } from 'vue';
4
+
5
+ // Mock the textarea's scrollHeight (jsdom always returns 0). The auto-grow +
6
+ // multi-line latch logic in CkPromptInput depends on scrollHeight; tests that
7
+ // exercise that path call this helper to stage a measured value.
8
+ function mockScrollHeight(value: number): void {
9
+ Object.defineProperty(HTMLTextAreaElement.prototype, 'scrollHeight', {
10
+ configurable: true,
11
+ get() {
12
+ return value;
13
+ },
14
+ });
15
+ }
16
+
17
+ function restoreScrollHeight(): void {
18
+ // Re-installing a property descriptor with a getter returning 0 mirrors
19
+ // jsdom's default (`HTMLElement.scrollHeight` returns 0 by spec there).
20
+ Object.defineProperty(HTMLTextAreaElement.prototype, 'scrollHeight', {
21
+ configurable: true,
22
+ get() {
23
+ return 0;
24
+ },
25
+ });
26
+ }
3
27
 
4
28
  describe('CkPromptInput', () => {
5
29
  describe('rendering', () => {
@@ -122,13 +146,10 @@ describe('CkPromptInput', () => {
122
146
  expect(screen.getByText('Express')).toBeInTheDocument();
123
147
  });
124
148
 
125
- it('does not render toolbar-left container when slot is not provided', () => {
149
+ it('renders no toolbar buttons when slots are not provided', () => {
126
150
  const { container } = render(CkPromptInput);
127
151
 
128
- const toolbarDivs = container.querySelectorAll(
129
- 'form > div:last-child > div',
130
- );
131
- expect(toolbarDivs[0]?.children.length).toBe(0);
152
+ expect(container.querySelectorAll('button')).toHaveLength(0);
132
153
  });
133
154
 
134
155
  it('renders toolbar-right slot content', () => {
@@ -139,13 +160,12 @@ describe('CkPromptInput', () => {
139
160
  expect(screen.getByText('Custom Send')).toBeInTheDocument();
140
161
  });
141
162
 
142
- it('renders nothing in toolbar-right by default', () => {
143
- const { container } = render(CkPromptInput);
163
+ it('renders toolbar-top slot content', () => {
164
+ render(CkPromptInput, {
165
+ slots: { 'toolbar-top': '<span>Toolbar-top content</span>' },
166
+ });
144
167
 
145
- const toolbarRight = container.querySelector(
146
- 'form > div:last-child > div.ml-auto',
147
- );
148
- expect(toolbarRight?.children.length).toBe(0);
168
+ expect(screen.getByText('Toolbar-top content')).toBeInTheDocument();
149
169
  });
150
170
 
151
171
  it('renders footer slot content', () => {
@@ -178,4 +198,179 @@ describe('CkPromptInput', () => {
178
198
  expect(screen.getByRole('textbox')).toBeDisabled();
179
199
  });
180
200
  });
201
+
202
+ // The form switches between compact (single-row, rounded-3xl) and stacked
203
+ // (multi-row, rounded-2xl) layouts based on (a) the presence of the
204
+ // `toolbar-top` slot and (b) whether the textarea content exceeds one line.
205
+ // These tests regression-guard the slot reactivity fix and the multi-line
206
+ // latch threshold.
207
+ describe('layout transitions', () => {
208
+ afterEach(() => {
209
+ restoreScrollHeight();
210
+ });
211
+
212
+ it('reacts to toolbar-top slot becoming available after mount', async () => {
213
+ const Parent = defineComponent({
214
+ components: { CkPromptInput },
215
+ setup() {
216
+ return { show: ref(false) };
217
+ },
218
+ template: `
219
+ <div>
220
+ <button data-testid="toggle" @click="show = !show">toggle</button>
221
+ <CkPromptInput model-value="">
222
+ <template v-if="show" #toolbar-top>
223
+ <span data-testid="top">attached files</span>
224
+ </template>
225
+ </CkPromptInput>
226
+ </div>
227
+ `,
228
+ });
229
+ const { container } = render(Parent);
230
+
231
+ expect(container.querySelector('[data-testid="top"]')).toBeNull();
232
+ expect(container.querySelector('form')?.className).toContain(
233
+ 'rounded-3xl',
234
+ );
235
+
236
+ await fireEvent.click(screen.getByTestId('toggle'));
237
+
238
+ // Parent re-render → `useSlots()` flips → computed re-evaluates → top
239
+ // row renders AND the form swaps to the smaller multi-row radius.
240
+ expect(
241
+ container.querySelector('[data-testid="top"]'),
242
+ ).toBeInTheDocument();
243
+ expect(container.querySelector('form')?.className).toContain(
244
+ 'rounded-2xl',
245
+ );
246
+ });
247
+
248
+ it('also reacts to toolbar-top slot becoming unavailable', async () => {
249
+ const Parent = defineComponent({
250
+ components: { CkPromptInput },
251
+ setup() {
252
+ return { show: ref(true) };
253
+ },
254
+ template: `
255
+ <div>
256
+ <button data-testid="toggle" @click="show = !show">toggle</button>
257
+ <CkPromptInput model-value="">
258
+ <template v-if="show" #toolbar-top>
259
+ <span data-testid="top">attached files</span>
260
+ </template>
261
+ </CkPromptInput>
262
+ </div>
263
+ `,
264
+ });
265
+ const { container } = render(Parent);
266
+
267
+ expect(container.querySelector('form')?.className).toContain(
268
+ 'rounded-2xl',
269
+ );
270
+
271
+ await fireEvent.click(screen.getByTestId('toggle'));
272
+
273
+ expect(container.querySelector('[data-testid="top"]')).toBeNull();
274
+ expect(container.querySelector('form')?.className).toContain(
275
+ 'rounded-3xl',
276
+ );
277
+ });
278
+
279
+ it('latches to multi-line layout when scrollHeight exceeds 2× line-height', async () => {
280
+ mockScrollHeight(48); // exactly 2 × LINE_HEIGHT
281
+ const Parent = defineComponent({
282
+ components: { CkPromptInput },
283
+ setup() {
284
+ return { text: ref('one') };
285
+ },
286
+ template: `
287
+ <CkPromptInput :model-value="text" @update:model-value="text = $event" />
288
+ `,
289
+ });
290
+ const { container, rerender } = render(Parent);
291
+ // Initial render mounted with scrollHeight=48 but modelValue='one' (no
292
+ // newline) — single line latches to multi when scrollHeight ≥ 48.
293
+ await nextTick();
294
+ // The grid template should now include the input-on-top + toolbar-row
295
+ // shape used for multi-line mode.
296
+ const form = container.querySelector('form');
297
+ expect(form?.style.gridTemplateAreas).toContain('"left . right"');
298
+
299
+ // Rerender to a different value to confirm the latch persists.
300
+ await rerender({});
301
+ await nextTick();
302
+ expect(form?.style.gridTemplateAreas).toContain('"left . right"');
303
+ });
304
+
305
+ it('does NOT latch on borderline scrollHeight (single emoji-line @ 36 px)', async () => {
306
+ mockScrollHeight(36); // 1.5 × LINE_HEIGHT — emoji/CJK sub-pixel rendering
307
+ const Parent = defineComponent({
308
+ components: { CkPromptInput },
309
+ setup() {
310
+ return { text: ref('hi 👋') };
311
+ },
312
+ template: `<CkPromptInput :model-value="text" />`,
313
+ });
314
+ const { container } = render(Parent);
315
+ await nextTick();
316
+ // Single-line layout: textarea + toolbars on the same row.
317
+ const form = container.querySelector('form');
318
+ expect(form?.style.gridTemplateAreas).toContain('"left input right"');
319
+ expect(form?.style.gridTemplateAreas).not.toContain('"left . right"');
320
+ });
321
+
322
+ it('latches when the user inserts an explicit newline (Shift+Enter)', async () => {
323
+ mockScrollHeight(24); // jsdom never reports >24 reliably; rely on \n
324
+ const Parent = defineComponent({
325
+ components: { CkPromptInput },
326
+ setup() {
327
+ return { text: ref('line one') };
328
+ },
329
+ template: `
330
+ <CkPromptInput :model-value="text" @update:model-value="text = $event" />
331
+ `,
332
+ });
333
+ const { container } = render(Parent);
334
+ await nextTick();
335
+ const form = container.querySelector('form');
336
+ expect(form?.style.gridTemplateAreas).toContain('"left input right"');
337
+
338
+ // Now the modelValue contains a newline.
339
+ const ParentTwoLines = defineComponent({
340
+ components: { CkPromptInput },
341
+ setup() {
342
+ return { text: ref('line one\nline two') };
343
+ },
344
+ template: `<CkPromptInput :model-value="text" />`,
345
+ });
346
+ const { container: c2 } = render(ParentTwoLines);
347
+ await nextTick();
348
+ expect(c2.querySelector('form')?.style.gridTemplateAreas).toContain(
349
+ '"left . right"',
350
+ );
351
+ });
352
+
353
+ it('un-latches multi-line state when the field is emptied', async () => {
354
+ mockScrollHeight(48);
355
+ const Parent = defineComponent({
356
+ components: { CkPromptInput },
357
+ setup() {
358
+ return { text: ref('something') };
359
+ },
360
+ template: `
361
+ <CkPromptInput :model-value="text" @update:model-value="text = $event" />
362
+ `,
363
+ });
364
+ const { container, getByRole } = render(Parent);
365
+ await nextTick();
366
+ const form = container.querySelector('form');
367
+ expect(form?.style.gridTemplateAreas).toContain('"left . right"');
368
+
369
+ mockScrollHeight(24);
370
+ await fireEvent.update(getByRole('textbox'), '');
371
+ await nextTick();
372
+
373
+ expect(form?.style.gridTemplateAreas).toContain('"left input right"');
374
+ });
375
+ });
181
376
  });