@aleph-alpha/chat-kit 5.3.0 → 5.4.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.
@@ -184,7 +184,7 @@ const _sfc_main$p = /* @__PURE__ */ defineComponent({
184
184
  const _hoisted_1$e = { class: "ck-citation-card flex w-[22.5rem] max-w-[90vw] flex-col gap-2 rounded-md bg-background-surface-floating px-6 pb-6 pt-4 shadow-md" };
185
185
  const _hoisted_2$8 = { class: "flex items-center gap-2" };
186
186
  const _hoisted_3$7 = ["title"];
187
- const _hoisted_4$6 = { class: "line-clamp-6 break-words text-sm leading-5 text-content-on-surface-secondary" };
187
+ const _hoisted_4$7 = { class: "line-clamp-6 break-words text-sm leading-5 text-content-on-surface-secondary" };
188
188
  const _hoisted_5$4 = {
189
189
  key: 0,
190
190
  class: "text-sm leading-5 text-content-on-surface-muted"
@@ -243,7 +243,7 @@ const _sfc_main$o = /* @__PURE__ */ defineComponent({
243
243
  _: 1
244
244
  }, 8, ["aria-label"])) : createCommentVNode("", true)
245
245
  ]),
246
- createElementVNode("p", _hoisted_4$6, toDisplayString(__props.snippet), 1),
246
+ createElementVNode("p", _hoisted_4$7, toDisplayString(__props.snippet), 1),
247
247
  __props.locationLabel ? (openBlock(), createElementBlock("p", _hoisted_5$4, toDisplayString(__props.locationLabel), 1)) : createCommentVNode("", true)
248
248
  ]);
249
249
  };
@@ -378,12 +378,13 @@ const _export_sfc = (sfc, props) => {
378
378
  return target;
379
379
  };
380
380
  const CkCitation = /* @__PURE__ */ _export_sfc(_sfc_main$n, [["__scopeId", "data-v-73025da4"]]);
381
- const _hoisted_1$c = ["data-message-row"];
382
- const _hoisted_2$7 = {
381
+ const _hoisted_1$c = { class: "relative flex min-h-0 flex-col overflow-hidden" };
382
+ const _hoisted_2$7 = ["data-message-row"];
383
+ const _hoisted_3$6 = {
383
384
  key: 0,
384
385
  class: "flex min-w-0 justify-end"
385
386
  };
386
- const _hoisted_3$6 = {
387
+ const _hoisted_4$6 = {
387
388
  key: 1,
388
389
  class: "flex min-w-0 justify-start"
389
390
  };
@@ -400,6 +401,8 @@ const _sfc_main$m = /* @__PURE__ */ defineComponent({
400
401
  const isAutoScrollEnabled = ref(true);
401
402
  const hasBottomSpacer = ref(false);
402
403
  const spacerHeight = ref(0);
404
+ const showTopFade = ref(false);
405
+ const showBottomFade = ref(false);
403
406
  let resizeObserver = null;
404
407
  onMounted(() => {
405
408
  const container = scrollContainerRef.value;
@@ -409,10 +412,12 @@ const _sfc_main$m = /* @__PURE__ */ defineComponent({
409
412
  for (const entry of entries) {
410
413
  containerHeight.value = entry.contentRect.height;
411
414
  }
415
+ updateFades();
412
416
  });
413
417
  resizeObserver.observe(container);
414
418
  nextTick(() => {
415
419
  container.scrollTop = container.scrollHeight;
420
+ updateFades();
416
421
  });
417
422
  }
418
423
  });
@@ -431,6 +436,15 @@ const _sfc_main$m = /* @__PURE__ */ defineComponent({
431
436
  }
432
437
  function handleScroll() {
433
438
  isAutoScrollEnabled.value = isFollowingContent();
439
+ updateFades();
440
+ }
441
+ function updateFades() {
442
+ const container = scrollContainerRef.value;
443
+ if (!container) return;
444
+ const { scrollTop, scrollHeight, clientHeight } = container;
445
+ const maxScroll = scrollHeight - clientHeight;
446
+ showTopFade.value = scrollTop > 1;
447
+ showBottomFade.value = scrollTop < maxScroll - 1;
434
448
  }
435
449
  function updateSpacerHeight() {
436
450
  const container = scrollContainerRef.value;
@@ -495,7 +509,10 @@ const _sfc_main$m = /* @__PURE__ */ defineComponent({
495
509
  if (newLen <= (oldLen ?? 0)) return;
496
510
  const lastMessage = props.messages[newLen - 1];
497
511
  if (lastMessage?.role === "user") {
498
- nextTick(() => scrollUserMessageToTop());
512
+ nextTick(() => {
513
+ scrollUserMessageToTop();
514
+ updateFades();
515
+ });
499
516
  }
500
517
  }
501
518
  );
@@ -510,37 +527,48 @@ const _sfc_main$m = /* @__PURE__ */ defineComponent({
510
527
  if (isAutoScrollEnabled.value) {
511
528
  scrollToEndOfContent();
512
529
  }
530
+ updateFades();
513
531
  });
514
532
  }
515
533
  },
516
534
  { deep: true }
517
535
  );
518
536
  return (_ctx, _cache) => {
519
- return openBlock(), createElementBlock("div", {
520
- ref_key: "scrollContainerRef",
521
- ref: scrollContainerRef,
522
- class: "relative flex flex-col gap-8 overflow-y-auto",
523
- onScroll: handleScroll
524
- }, [
525
- (openBlock(true), createElementBlock(Fragment, null, renderList(__props.messages, (message) => {
526
- return openBlock(), createElementBlock("div", {
527
- key: message.id,
528
- "data-message-row": message.role,
529
- class: "min-w-0"
530
- }, [
531
- message.role === "user" ? (openBlock(), createElementBlock("div", _hoisted_2$7, [
532
- renderSlot(_ctx.$slots, "user-message", { message })
533
- ])) : (openBlock(), createElementBlock("div", _hoisted_3$6, [
534
- renderSlot(_ctx.$slots, "agent-message", { message })
535
- ]))
536
- ], 8, _hoisted_1$c);
537
- }), 128)),
538
- hasBottomSpacer.value ? (openBlock(), createElementBlock("div", {
539
- key: 0,
537
+ return openBlock(), createElementBlock("div", _hoisted_1$c, [
538
+ createElementVNode("div", {
539
+ "aria-hidden": "true",
540
+ class: normalizeClass(["pointer-events-none absolute inset-x-0 top-0 z-10 h-8 bg-gradient-to-b from-background-surface-primary to-transparent transition-opacity duration-200", showTopFade.value ? "opacity-100" : "opacity-0"])
541
+ }, null, 2),
542
+ createElementVNode("div", {
543
+ ref_key: "scrollContainerRef",
544
+ ref: scrollContainerRef,
545
+ class: "relative flex min-h-0 flex-1 flex-col gap-8 overflow-y-auto",
546
+ onScroll: handleScroll
547
+ }, [
548
+ (openBlock(true), createElementBlock(Fragment, null, renderList(__props.messages, (message) => {
549
+ return openBlock(), createElementBlock("div", {
550
+ key: message.id,
551
+ "data-message-row": message.role,
552
+ class: "min-w-0"
553
+ }, [
554
+ message.role === "user" ? (openBlock(), createElementBlock("div", _hoisted_3$6, [
555
+ renderSlot(_ctx.$slots, "user-message", { message })
556
+ ])) : (openBlock(), createElementBlock("div", _hoisted_4$6, [
557
+ renderSlot(_ctx.$slots, "agent-message", { message })
558
+ ]))
559
+ ], 8, _hoisted_2$7);
560
+ }), 128)),
561
+ hasBottomSpacer.value ? (openBlock(), createElementBlock("div", {
562
+ key: 0,
563
+ "aria-hidden": "true",
564
+ style: normalizeStyle({ minHeight: `${spacerHeight.value}px` })
565
+ }, null, 4)) : createCommentVNode("", true)
566
+ ], 544),
567
+ createElementVNode("div", {
540
568
  "aria-hidden": "true",
541
- style: normalizeStyle({ minHeight: `${spacerHeight.value}px` })
542
- }, null, 4)) : createCommentVNode("", true)
543
- ], 544);
569
+ class: normalizeClass(["pointer-events-none absolute inset-x-0 bottom-0 z-10 h-8 bg-gradient-to-t from-background-surface-primary to-transparent transition-opacity duration-200", showBottomFade.value ? "opacity-100" : "opacity-0"])
570
+ }, null, 2)
571
+ ]);
544
572
  };
545
573
  }
546
574
  });
@@ -1931,7 +1959,7 @@ const _sfc_main$c = /* @__PURE__ */ defineComponent({
1931
1959
  };
1932
1960
  }
1933
1961
  });
1934
- const baseClass = "flex w-full min-w-0 flex-col p-2";
1962
+ const baseClass = "flex w-full min-w-0 flex-col py-2";
1935
1963
  const _sfc_main$b = /* @__PURE__ */ defineComponent({
1936
1964
  ...{
1937
1965
  name: "CkHistoryWrapper",
@@ -2706,7 +2734,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
2706
2734
  showActionOnHover: { type: Boolean, default: true },
2707
2735
  timeFormatter: { type: Function, default: formatRelativeTime },
2708
2736
  collapsedLimit: { default: 5 },
2709
- maxHeight: { default: void 0 }
2737
+ maxHeight: { default: void 0 },
2738
+ contentClass: { default: void 0 }
2710
2739
  },
2711
2740
  emits: ["select", "edit", "delete"],
2712
2741
  setup(__props, { emit: __emit }) {
@@ -2744,7 +2773,10 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
2744
2773
  return (_ctx, _cache) => {
2745
2774
  return openBlock(), createElementBlock("div", null, [
2746
2775
  createVNode(_sfc_main$b, {
2747
- class: normalizeClass(isExpanded.value && props.maxHeight ? "overflow-y-auto" : void 0),
2776
+ class: normalizeClass([
2777
+ isExpanded.value && props.maxHeight ? "overflow-y-auto" : void 0,
2778
+ props.contentClass
2779
+ ]),
2748
2780
  style: normalizeStyle(
2749
2781
  isExpanded.value && props.maxHeight ? { maxHeight: props.maxHeight } : void 0
2750
2782
  )
@@ -1 +1 @@
1
- {"version":3,"file":"CkConversationLayout.vue.d.ts","sourceRoot":"","sources":["../../../../src/components/base/CkConversationLayout/CkConversationLayout.vue"],"names":[],"mappings":"AAoNA,QAAA,MAAM,YAAY,GAAK,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC5D,aAAa,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAC9D,YAAY,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,EAC3G,gBAAgB,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,EAClE;WA6PO,OAAO,KAAK,EAAE,WAAW,GAAG,mBAAmB;kBAzP3C,CAAC,EAAE;MAyP2D,GAAG,CAAC,OAAO,UAAU,SAAS;QAAE,oBAAoB,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAC1I,CAAC,OAAO,EAAE,EAAE,KAAK,IAAI;WACtB,GAAG;;wBAtPO,CAAC,KAAK,EAAE;YAAE,OAAO,EAAE,CAAC,CAAA;SAAE,KAAK,IAAI;yBAC9B,CAAC,KAAK,EAAE;YAAE,OAAO,EAAE,CAAC,CAAA;SAAE,KAAK,IAAI;;UAuP3C,EAAE;EAEL,KACQ,OAAO,KAAK,EAAE,KAAK,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC,OAAO,WAAW,CAAC,CAAA;CAAI,CAAC;wBACvD,OAAO,YAAY;AAAxC,wBAAyC;AACzC,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,CAAC,GAAG,EAAE,CAAC"}
1
+ {"version":3,"file":"CkConversationLayout.vue.d.ts","sourceRoot":"","sources":["../../../../src/components/base/CkConversationLayout/CkConversationLayout.vue"],"names":[],"mappings":"AA+PA,QAAA,MAAM,YAAY,GAAK,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC5D,aAAa,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAC9D,YAAY,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,EAC3G,gBAAgB,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,EAClE;WAsUO,OAAO,KAAK,EAAE,WAAW,GAAG,mBAAmB;kBAlU3C,CAAC,EAAE;MAkU2D,GAAG,CAAC,OAAO,UAAU,SAAS;QAAE,oBAAoB,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAC1I,CAAC,OAAO,EAAE,EAAE,KAAK,IAAI;WACtB,GAAG;;wBA/TO,CAAC,KAAK,EAAE;YAAE,OAAO,EAAE,CAAC,CAAA;SAAE,KAAK,IAAI;yBAC9B,CAAC,KAAK,EAAE;YAAE,OAAO,EAAE,CAAC,CAAA;SAAE,KAAK,IAAI;;UAgU3C,EAAE;EAEL,KACQ,OAAO,KAAK,EAAE,KAAK,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC,OAAO,WAAW,CAAC,CAAA;CAAI,CAAC;wBACvD,OAAO,YAAY;AAAxC,wBAAyC;AACzC,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,CAAC,GAAG,EAAE,CAAC"}
@@ -44,6 +44,7 @@ declare const __VLS_base: import("vue").DefineComponent<CkHistoryProps, {}, {},
44
44
  timeFormatter: (updatedAtSeconds: number) => string;
45
45
  collapsedLimit: number;
46
46
  maxHeight: string;
47
+ contentClass: string;
47
48
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
48
49
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
49
50
  declare const _default: typeof __VLS_export;
@@ -1 +1 @@
1
- {"version":3,"file":"CkHistory.vue.d.ts","sourceRoot":"","sources":["../../../../src/components/composed/CkHistory/CkHistory.vue"],"names":[],"mappings":"AA+LA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,SAAS,CAAC;AAiB9D,KAAK,WAAW,GAAG;IACjB;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,EAAE,CAAC,KAAK,EAAE;QACZ,YAAY,EAAE,YAAY,CAAC;QAC3B,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,OAAO,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;QACzB,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;QACpC,IAAI,EAAE,MAAM,IAAI,CAAC;QACjB,MAAM,EAAE,MAAM,IAAI,CAAC;KACpB,KAAK,IAAI,CAAC;CACZ,CAAC;AA+SF,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;6EAId,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AAWzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
1
+ {"version":3,"file":"CkHistory.vue.d.ts","sourceRoot":"","sources":["../../../../src/components/composed/CkHistory/CkHistory.vue"],"names":[],"mappings":"AAmMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,SAAS,CAAC;AAkB9D,KAAK,WAAW,GAAG;IACjB;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,EAAE,CAAC,KAAK,EAAE;QACZ,YAAY,EAAE,YAAY,CAAC;QAC3B,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,OAAO,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;QACzB,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;QACpC,IAAI,EAAE,MAAM,IAAI,CAAC;QACjB,MAAM,EAAE,MAAM,IAAI,CAAC;KACpB,KAAK,IAAI,CAAC;CACZ,CAAC;AAsTF,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;;6EAId,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AAWzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
@@ -69,7 +69,7 @@ export interface CkHistoryProps {
69
69
  * below the visible rows. Clicking it expands the list and makes it
70
70
  * scrollable (capped at `maxHeight`).
71
71
  *
72
- * Set to `0` or `undefined` to disable collapsing — all conversations
72
+ * Set to `0` to disable collapsing — all conversations
73
73
  * are shown and the list is always scrollable.
74
74
  *
75
75
  * @default 5
@@ -82,6 +82,14 @@ export interface CkHistoryProps {
82
82
  * @default undefined — no max-height constraint
83
83
  */
84
84
  maxHeight?: string;
85
+ /**
86
+ * Additional CSS class(es) applied to the inner `CkHistoryWrapper` that
87
+ * wraps the conversation rows. Useful for adjusting padding, spacing, or
88
+ * other layout concerns of the list container.
89
+ *
90
+ * @default undefined
91
+ */
92
+ contentClass?: string;
85
93
  }
86
94
  /**
87
95
  * Emitted events for `CkHistory`.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/components/composed/CkHistory/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B;;;;;;;;;;;;OAYG;IACH,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,EAAE,CAAC,gBAAgB,EAAE,MAAM,KAAK,MAAM,CAAC;IACrD;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;OAOG;IACH,MAAM,EAAE,CAAC,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;IACxD,0EAA0E;IAC1E,IAAI,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;IACnC,4EAA4E;IAC5E,MAAM,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;CACtC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/components/composed/CkHistory/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B;;;;;;;;;;;;OAYG;IACH,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,EAAE,CAAC,gBAAgB,EAAE,MAAM,KAAK,MAAM,CAAC;IACrD;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;OAOG;IACH,MAAM,EAAE,CAAC,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;IACxD,0EAA0E;IAC1E,IAAI,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;IACnC,4EAA4E;IAC5E,MAAM,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;CACtC"}
@@ -1,4 +1,4 @@
1
- import { _, a, C, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z } from "../CkHistory.vue_vue_type_script_setup_true_lang-BcYJmbCd.js";
1
+ import { _, a, C, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z } from "../CkHistory.vue_vue_type_script_setup_true_lang-D9oP6oEj.js";
2
2
  export {
3
3
  _ as CkAgentMessage,
4
4
  a as CkAgentMessageHeader,
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { u } from "./useMessageHandler-N50Yj-NU.js";
2
2
  import { C, d, p, u as u2, a } from "./useChatKitLabels-DLy0BFx8.js";
3
3
  import { S, c, d as d2, a as a2, u as u3, b, e, f } from "./index-CyrVnbS9.js";
4
- import { _, a as a3, C as C2, b as b2, c as c2, d as d3, e as e2, f as f2, g, h, i, j, k, l, m, n, o, p as p2, q, r, s, t, u as u4, v, w, x, y, z, A } from "./CkHistory.vue_vue_type_script_setup_true_lang-BcYJmbCd.js";
4
+ import { _, a as a3, C as C2, b as b2, c as c2, d as d3, e as e2, f as f2, g, h, i, j, k, l, m, n, o, p as p2, q, r, s, t, u as u4, v, w, x, y, z, A } from "./CkHistory.vue_vue_type_script_setup_true_lang-D9oP6oEj.js";
5
5
  export {
6
6
  C as CHAT_KIT_LABELS,
7
7
  _ as CkAgentMessage,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aleph-alpha/chat-kit",
3
- "version": "5.3.0",
3
+ "version": "5.4.0",
4
4
  "description": "Chat kit package for building chat interfaces with Vue",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,5 +1,5 @@
1
1
  import CkConversationLayout from './CkConversationLayout.vue';
2
- import { render, screen } from '@testing-library/vue';
2
+ import { fireEvent, render, screen } from '@testing-library/vue';
3
3
  import { type Component, defineComponent, h } from 'vue';
4
4
 
5
5
  const messages = [
@@ -126,4 +126,87 @@ describe('CkConversationLayout', () => {
126
126
  expect(screen.getByText('42|2026-02-13|Hi')).toBeInTheDocument();
127
127
  });
128
128
  });
129
+
130
+ describe('scroll fade indicators', () => {
131
+ function getFadeElements(container: HTMLElement) {
132
+ return {
133
+ topFade: container.querySelector('.bg-gradient-to-b') as HTMLElement,
134
+ bottomFade: container.querySelector('.bg-gradient-to-t') as HTMLElement,
135
+ scrollEl: container.querySelector(
136
+ '[class*="overflow-y-auto"]',
137
+ ) as HTMLElement,
138
+ };
139
+ }
140
+
141
+ function setScrollMetrics(
142
+ el: HTMLElement,
143
+ {
144
+ scrollHeight,
145
+ clientHeight,
146
+ }: { scrollHeight: number; clientHeight: number },
147
+ ) {
148
+ Object.defineProperty(el, 'scrollHeight', {
149
+ configurable: true,
150
+ value: scrollHeight,
151
+ });
152
+ Object.defineProperty(el, 'clientHeight', {
153
+ configurable: true,
154
+ value: clientHeight,
155
+ });
156
+ }
157
+
158
+ it('renders decorative top and bottom fade overlays', () => {
159
+ const { container } = renderLayout();
160
+ const { topFade, bottomFade } = getFadeElements(container);
161
+
162
+ expect(topFade).toBeTruthy();
163
+ expect(bottomFade).toBeTruthy();
164
+ expect(topFade.getAttribute('aria-hidden')).toBe('true');
165
+ expect(bottomFade.getAttribute('aria-hidden')).toBe('true');
166
+ });
167
+
168
+ it('hides both fades when the content is not scrollable', () => {
169
+ const { container } = renderLayout();
170
+ const { topFade, bottomFade } = getFadeElements(container);
171
+
172
+ expect(topFade.className).toContain('opacity-0');
173
+ expect(bottomFade.className).toContain('opacity-0');
174
+ });
175
+
176
+ it('shows both fades when scrolled into the middle of the content', async () => {
177
+ const { container } = renderLayout();
178
+ const { topFade, bottomFade, scrollEl } = getFadeElements(container);
179
+
180
+ setScrollMetrics(scrollEl, { scrollHeight: 1000, clientHeight: 400 });
181
+ scrollEl.scrollTop = 200;
182
+ await fireEvent.scroll(scrollEl);
183
+
184
+ expect(topFade.className).toContain('opacity-100');
185
+ expect(bottomFade.className).toContain('opacity-100');
186
+ });
187
+
188
+ it('hides the top fade at the start of the conversation', async () => {
189
+ const { container } = renderLayout();
190
+ const { topFade, bottomFade, scrollEl } = getFadeElements(container);
191
+
192
+ setScrollMetrics(scrollEl, { scrollHeight: 1000, clientHeight: 400 });
193
+ scrollEl.scrollTop = 0;
194
+ await fireEvent.scroll(scrollEl);
195
+
196
+ expect(topFade.className).toContain('opacity-0');
197
+ expect(bottomFade.className).toContain('opacity-100');
198
+ });
199
+
200
+ it('hides the bottom fade at the end of the conversation', async () => {
201
+ const { container } = renderLayout();
202
+ const { topFade, bottomFade, scrollEl } = getFadeElements(container);
203
+
204
+ setScrollMetrics(scrollEl, { scrollHeight: 1000, clientHeight: 400 });
205
+ scrollEl.scrollTop = 600;
206
+ await fireEvent.scroll(scrollEl);
207
+
208
+ expect(topFade.className).toContain('opacity-100');
209
+ expect(bottomFade.className).toContain('opacity-0');
210
+ });
211
+ });
129
212
  });
@@ -18,6 +18,11 @@ const isAutoScrollEnabled = ref(true);
18
18
  const hasBottomSpacer = ref(false);
19
19
  const spacerHeight = ref(0);
20
20
 
21
+ // Whether there is scrollable content above / below the current viewport.
22
+ // Drives the top / bottom fade overlays that hint at more content to scroll to.
23
+ const showTopFade = ref(false);
24
+ const showBottomFade = ref(false);
25
+
21
26
  let resizeObserver: ResizeObserver | null = null;
22
27
 
23
28
  onMounted(() => {
@@ -28,12 +33,14 @@ onMounted(() => {
28
33
  for (const entry of entries) {
29
34
  containerHeight.value = entry.contentRect.height;
30
35
  }
36
+ updateFades();
31
37
  });
32
38
  resizeObserver.observe(container);
33
39
 
34
40
  // Initially scroll to the bottom of the conversation without any spacer
35
41
  nextTick(() => {
36
42
  container.scrollTop = container.scrollHeight;
43
+ updateFades();
37
44
  });
38
45
  }
39
46
  });
@@ -60,6 +67,24 @@ function isFollowingContent(): boolean {
60
67
 
61
68
  function handleScroll(): void {
62
69
  isAutoScrollEnabled.value = isFollowingContent();
70
+ updateFades();
71
+ }
72
+
73
+ /**
74
+ * Update the visibility of the top / bottom fade overlays based on the current
75
+ * scroll position. The top fade appears once the user has scrolled away from
76
+ * the start; the bottom fade appears while there is still content below the
77
+ * viewport. When the content fits entirely, both fades stay hidden.
78
+ */
79
+ function updateFades(): void {
80
+ const container = scrollContainerRef.value;
81
+ if (!container) return;
82
+
83
+ const { scrollTop, scrollHeight, clientHeight } = container;
84
+ const maxScroll = scrollHeight - clientHeight;
85
+
86
+ showTopFade.value = scrollTop > 1;
87
+ showBottomFade.value = scrollTop < maxScroll - 1;
63
88
  }
64
89
 
65
90
  /**
@@ -153,7 +178,10 @@ watch(
153
178
  if (newLen <= (oldLen ?? 0)) return;
154
179
  const lastMessage = props.messages[newLen - 1];
155
180
  if (lastMessage?.role === 'user') {
156
- nextTick(() => scrollUserMessageToTop());
181
+ nextTick(() => {
182
+ scrollUserMessageToTop();
183
+ updateFades();
184
+ });
157
185
  }
158
186
  },
159
187
  );
@@ -171,6 +199,7 @@ watch(
171
199
  if (isAutoScrollEnabled.value) {
172
200
  scrollToEndOfContent();
173
201
  }
202
+ updateFades();
174
203
  });
175
204
  }
176
205
  },
@@ -179,29 +208,43 @@ watch(
179
208
  </script>
180
209
 
181
210
  <template>
182
- <div
183
- ref="scrollContainerRef"
184
- class="relative flex flex-col gap-8 overflow-y-auto"
185
- @scroll="handleScroll"
186
- >
211
+ <div class="relative flex min-h-0 flex-col overflow-hidden">
212
+ <!-- Top fade: hints that there is more content above the viewport -->
187
213
  <div
188
- v-for="message in messages"
189
- :key="message.id"
190
- :data-message-row="message.role"
191
- class="min-w-0"
214
+ aria-hidden="true"
215
+ class="pointer-events-none absolute inset-x-0 top-0 z-10 h-8 bg-gradient-to-b from-background-surface-primary to-transparent transition-opacity duration-200"
216
+ :class="showTopFade ? 'opacity-100' : 'opacity-0'"
217
+ />
218
+ <div
219
+ ref="scrollContainerRef"
220
+ class="relative flex min-h-0 flex-1 flex-col gap-8 overflow-y-auto"
221
+ @scroll="handleScroll"
192
222
  >
193
- <div v-if="message.role === 'user'" class="flex min-w-0 justify-end">
194
- <slot name="user-message" :message="message" />
195
- </div>
196
- <div v-else class="flex min-w-0 justify-start">
197
- <slot name="agent-message" :message="message" />
223
+ <div
224
+ v-for="message in messages"
225
+ :key="message.id"
226
+ :data-message-row="message.role"
227
+ class="min-w-0"
228
+ >
229
+ <div v-if="message.role === 'user'" class="flex min-w-0 justify-end">
230
+ <slot name="user-message" :message="message" />
231
+ </div>
232
+ <div v-else class="flex min-w-0 justify-start">
233
+ <slot name="agent-message" :message="message" />
234
+ </div>
198
235
  </div>
236
+ <!-- Bottom spacer: shrinks as agent content grows to keep viewport stable -->
237
+ <div
238
+ v-if="hasBottomSpacer"
239
+ aria-hidden="true"
240
+ :style="{ minHeight: `${spacerHeight}px` }"
241
+ />
199
242
  </div>
200
- <!-- Bottom spacer: shrinks as agent content grows to keep viewport stable -->
243
+ <!-- Bottom fade: hints that there is more content below the viewport -->
201
244
  <div
202
- v-if="hasBottomSpacer"
203
245
  aria-hidden="true"
204
- :style="{ minHeight: `${spacerHeight}px` }"
246
+ class="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-8 bg-gradient-to-t from-background-surface-primary to-transparent transition-opacity duration-200"
247
+ :class="showBottomFade ? 'opacity-100' : 'opacity-0'"
205
248
  />
206
249
  </div>
207
250
  </template>
@@ -13,7 +13,7 @@ const props = withDefaults(defineProps<CkHistoryWrapperProps>(), {
13
13
 
14
14
  const attrs = useAttrs();
15
15
 
16
- const baseClass = 'flex w-full min-w-0 flex-col p-2';
16
+ const baseClass = 'flex w-full min-w-0 flex-col py-2';
17
17
 
18
18
  const rootClass = computed(() => {
19
19
  const consumerClass =
@@ -433,4 +433,34 @@ describe('CkHistory', () => {
433
433
  expect(emitted().select).toBeUndefined();
434
434
  });
435
435
  });
436
+
437
+ describe('contentClass', () => {
438
+ it('applies contentClass to the inner history wrapper', () => {
439
+ const { container } = render(CkHistory, {
440
+ props: {
441
+ conversations: [makeConversation({ id: 'c-1' })],
442
+ hrefResolver,
443
+ contentClass: 'px-2 custom-content',
444
+ },
445
+ });
446
+
447
+ const wrapper = container.querySelector('[data-slot="chat-history"]');
448
+ expect(wrapper).toBeTruthy();
449
+ expect(wrapper?.className).toMatch(/px-2/);
450
+ expect(wrapper?.className).toMatch(/custom-content/);
451
+ });
452
+
453
+ it('does not add any content class to the wrapper by default', () => {
454
+ const { container } = render(CkHistory, {
455
+ props: {
456
+ conversations: [makeConversation({ id: 'c-1' })],
457
+ hrefResolver,
458
+ },
459
+ });
460
+
461
+ const wrapper = container.querySelector('[data-slot="chat-history"]');
462
+ expect(wrapper).toBeTruthy();
463
+ expect(wrapper?.className).not.toMatch(/px-2/);
464
+ });
465
+ });
436
466
  });
@@ -22,6 +22,7 @@ const props = withDefaults(defineProps<CkHistoryProps>(), {
22
22
  timeFormatter: formatRelativeTime,
23
23
  collapsedLimit: 5,
24
24
  maxHeight: undefined,
25
+ contentClass: undefined,
25
26
  });
26
27
 
27
28
  const emit = defineEmits<CkHistoryEmits>();
@@ -106,7 +107,10 @@ function handleRowClick(c: Conversation, event: MouseEvent) {
106
107
  <template>
107
108
  <div>
108
109
  <CkHistoryWrapper
109
- :class="isExpanded && props.maxHeight ? 'overflow-y-auto' : undefined"
110
+ :class="[
111
+ isExpanded && props.maxHeight ? 'overflow-y-auto' : undefined,
112
+ props.contentClass,
113
+ ]"
110
114
  :style="
111
115
  isExpanded && props.maxHeight
112
116
  ? { maxHeight: props.maxHeight }
@@ -70,7 +70,7 @@ export interface CkHistoryProps {
70
70
  * below the visible rows. Clicking it expands the list and makes it
71
71
  * scrollable (capped at `maxHeight`).
72
72
  *
73
- * Set to `0` or `undefined` to disable collapsing — all conversations
73
+ * Set to `0` to disable collapsing — all conversations
74
74
  * are shown and the list is always scrollable.
75
75
  *
76
76
  * @default 5
@@ -83,6 +83,14 @@ export interface CkHistoryProps {
83
83
  * @default undefined — no max-height constraint
84
84
  */
85
85
  maxHeight?: string;
86
+ /**
87
+ * Additional CSS class(es) applied to the inner `CkHistoryWrapper` that
88
+ * wraps the conversation rows. Useful for adjusting padding, spacing, or
89
+ * other layout concerns of the list container.
90
+ *
91
+ * @default undefined
92
+ */
93
+ contentClass?: string;
86
94
  }
87
95
 
88
96
  /**