@kodaris/krubble-app-components 1.0.147 → 1.0.149

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.
@@ -2897,6 +2897,13 @@ class KRChatbot extends i$3 {
2897
2897
  */
2898
2898
  this._reconnectAttempts = 0;
2899
2899
  this._userHasScrolledUp = false;
2900
+ /**
2901
+ * Keep the end of the streaming response in view. Set at the start of every
2902
+ * turn (send, confirmation decision, reconnect); cleared when the user scrolls
2903
+ * up on their own (wheel, touch drag, scrollbar drag) and restored when they
2904
+ * scroll back to the bottom. Checked in updated() after every streamed chunk.
2905
+ */
2906
+ this._followStream = false;
2900
2907
  this._isDragging = false;
2901
2908
  this._isResizing = false;
2902
2909
  this._overlay = null;
@@ -3552,6 +3559,35 @@ class KRChatbot extends i$3 {
3552
3559
  });
3553
3560
  }
3554
3561
  }
3562
+ // Streamed chunks mutate the message in place and call requestUpdate(), so
3563
+ // `changed` never has 'messages' mid-turn — run the follow on every update
3564
+ // instead. The user message stays pinned at the top (Gemini-style) until the
3565
+ // response grows past the bottom of the view; from then on the end stays in
3566
+ // view. Never scrolls to the empty runway below the content.
3567
+ if (this._isStreaming && this._followStream && this._messagesEl) {
3568
+ requestAnimationFrame(() => {
3569
+ const container = this._messagesEl;
3570
+ if (!container || !this._isStreaming || !this._followStream) {
3571
+ return;
3572
+ }
3573
+ const turnEls = container.querySelectorAll('.agent-turn');
3574
+ const turnEl = turnEls[turnEls.length - 1];
3575
+ const lastEl = turnEl?.lastElementChild;
3576
+ if (!lastEl) {
3577
+ return;
3578
+ }
3579
+ // Bottom edge of the turn's last rendered child, in the container's
3580
+ // scroll coordinates — the runway (min-height on the turn) sits below it.
3581
+ const contentBottom = lastEl.getBoundingClientRect().bottom
3582
+ - container.getBoundingClientRect().top
3583
+ + container.scrollTop
3584
+ + 24;
3585
+ const visibleBottom = container.scrollTop + container.clientHeight;
3586
+ if (contentBottom > visibleBottom) {
3587
+ container.scrollTop = Math.min(contentBottom - container.clientHeight, container.scrollHeight - container.clientHeight);
3588
+ }
3589
+ });
3590
+ }
3555
3591
  if (changed.has('opened') && this.opened && this._inputEl) {
3556
3592
  setTimeout(() => {
3557
3593
  if (this._inputEl) {
@@ -4620,6 +4656,7 @@ class KRChatbot extends i$3 {
4620
4656
  this._saveState();
4621
4657
  this._error = '';
4622
4658
  this._userHasScrolledUp = false;
4659
+ this._followStream = true;
4623
4660
  // Clear the composer, but DON'T revoke the preview object URLs — the sent
4624
4661
  // message now renders them until the upload's real files replace them.
4625
4662
  this._pendingConversationFiles = [];
@@ -4854,6 +4891,7 @@ class KRChatbot extends i$3 {
4854
4891
  this._turnFinishedCleanly = false;
4855
4892
  this._reconnectAttempts = 0;
4856
4893
  this._userHasScrolledUp = false;
4894
+ this._followStream = true;
4857
4895
  this.send({
4858
4896
  message: '',
4859
4897
  messages: this.messages,
@@ -4954,6 +4992,33 @@ class KRChatbot extends i$3 {
4954
4992
  }
4955
4993
  else {
4956
4994
  this._userHasScrolledUp = false;
4995
+ // Back at the bottom mid-turn: resume following the stream end.
4996
+ if (this._isStreaming) {
4997
+ this._followStream = true;
4998
+ }
4999
+ }
5000
+ }
5001
+ /**
5002
+ * User-driven scroll intent while a response streams. Scroll events alone can't
5003
+ * tell the user apart from our own follow-scroll (and from scrollTop clamping
5004
+ * when the runway clears), so listen for the gestures instead: a wheel/trackpad
5005
+ * move up, any touch drag, or a pointer down on the scrollbar track. Any of
5006
+ * these stops the follow; scrolling back to the bottom restores it.
5007
+ */
5008
+ _handleMessagesWheel(e) {
5009
+ if (this._isStreaming && e.deltaY < 0) {
5010
+ this._followStream = false;
5011
+ }
5012
+ }
5013
+ _handleMessagesTouchMove() {
5014
+ if (this._isStreaming) {
5015
+ this._followStream = false;
5016
+ }
5017
+ }
5018
+ _handleMessagesPointerDown(e) {
5019
+ const container = e.currentTarget;
5020
+ if (this._isStreaming && e.offsetX >= container.clientWidth) {
5021
+ this._followStream = false;
4957
5022
  }
4958
5023
  }
4959
5024
  _handleErrorDismiss() {
@@ -5526,6 +5591,7 @@ class KRChatbot extends i$3 {
5526
5591
  // (a no-op).
5527
5592
  this._reconnectAttempts = 0;
5528
5593
  this._isStreaming = true;
5594
+ this._followStream = true;
5529
5595
  this._reader = response.body.getReader();
5530
5596
  this._decoder = new TextDecoder();
5531
5597
  this._buffer = '';
@@ -6644,7 +6710,13 @@ class KRChatbot extends i$3 {
6644
6710
  </div>
6645
6711
  </div>
6646
6712
 
6647
- <div class="messages" @scroll=${this._handleMessagesScroll}>
6713
+ <div
6714
+ class="messages"
6715
+ @scroll=${this._handleMessagesScroll}
6716
+ @wheel=${this._handleMessagesWheel}
6717
+ @touchmove=${this._handleMessagesTouchMove}
6718
+ @pointerdown=${this._handleMessagesPointerDown}
6719
+ >
6648
6720
  <div class="messages-inner">
6649
6721
  ${this.messages.length === 0 ? b `
6650
6722
  <div class="empty">
@@ -11544,6 +11616,7 @@ var __decorate$3 = (undefined && undefined.__decorate) || function (decorators,
11544
11616
  * activeId="overview"
11545
11617
  * .actions=${[
11546
11618
  * { id: 'edit', label: 'Edit', variant: 'flat' },
11619
+ * { id: 'preview', label: 'Preview', href: '/s/companies/123', target: '_blank' },
11547
11620
  * { id: 'archive', label: 'Archive' },
11548
11621
  * { id: 'delete', label: 'Delete' },
11549
11622
  * { id: 'export', label: 'Export' },
@@ -11555,7 +11628,8 @@ var __decorate$3 = (undefined && undefined.__decorate) || function (decorators,
11555
11628
  *
11556
11629
  * Actions render in a footer at the bottom of the nav column. With more than
11557
11630
  * `maxVisibleActions` (default 5), the overflow collapses into a "More" dropdown.
11558
- * Listen for `action-click` to handle a click: `e.detail.action` is the action id.
11631
+ * Listen for `action-click` to handle a click: `e.detail.action` is the action id. An action with
11632
+ * an `href` renders as a link and navigates on its own; it does not fire `action-click`.
11559
11633
  *
11560
11634
  * @slot - The main screen content
11561
11635
  *
@@ -11566,7 +11640,7 @@ var __decorate$3 = (undefined && undefined.__decorate) || function (decorators,
11566
11640
  * @property {KRScreenNavAction[]} actions - Action buttons rendered in the nav footer
11567
11641
  * @property {number} maxVisibleActions - Max inline actions before collapsing into a "More" dropdown (default 5)
11568
11642
  *
11569
- * @fires action-click - When an action button (inline or overflow) is clicked; detail is `{ action: id }`
11643
+ * @fires action-click - When an action button (inline or overflow) without an `href` is clicked; detail is `{ action: id }`
11570
11644
  */
11571
11645
  class KRScreenNav extends i$3 {
11572
11646
  constructor() {
@@ -11660,6 +11734,10 @@ class KRScreenNav extends i$3 {
11660
11734
  }
11661
11735
  }
11662
11736
  handleActionClick(e, action) {
11737
+ // A link action navigates on its own through the kr-button's anchor; it doesn't fire action-click.
11738
+ if (action.href) {
11739
+ return;
11740
+ }
11663
11741
  this.dispatchEvent(new CustomEvent('action-click', {
11664
11742
  detail: { action: action.id },
11665
11743
  bubbles: true,
@@ -11686,6 +11764,8 @@ class KRScreenNav extends i$3 {
11686
11764
  overflowOptions = this.actions.slice(this.maxVisibleActions - 1).map((action) => ({
11687
11765
  id: action.id,
11688
11766
  label: action.label,
11767
+ href: action.href,
11768
+ hrefTarget: action.target,
11689
11769
  }));
11690
11770
  }
11691
11771
  return b `
@@ -11721,6 +11801,8 @@ class KRScreenNav extends i$3 {
11721
11801
  class="nav-actions__button"
11722
11802
  variant=${action.variant || 'outline'}
11723
11803
  size="small"
11804
+ href=${action.href || A}
11805
+ target=${action.target || A}
11724
11806
  @click=${(e) => this.handleActionClick(e, action)}
11725
11807
  >
11726
11808
  ${action.label}
@@ -12276,9 +12358,20 @@ class KRRouter {
12276
12358
  // its element so the whole chain can be merged once they've all settled.
12277
12359
  Promise.all(matched.routes.map(match => {
12278
12360
  // Reuse the cached element for this url if we have one. Its data is cached with it — resolve()
12279
- // doesn't run again, but the level still has to contribute to the merge below.
12361
+ // doesn't run again unless the route asks for it with runResolvers: 'always' — but the level still
12362
+ // has to contribute to the merge below.
12280
12363
  const cached = KRRouter.routeCache.get(match.url);
12281
12364
  if (cached) {
12365
+ const cachedConstructor = customElements.get(match.route.component);
12366
+ if (match.route.runResolvers === 'always' && cachedConstructor && typeof cachedConstructor.resolve === 'function') {
12367
+ return Promise.resolve(cachedConstructor.resolve(KRRouter.getParams())).then(data => {
12368
+ cached.data = data;
12369
+ return {
12370
+ el: cached.el,
12371
+ data,
12372
+ };
12373
+ });
12374
+ }
12282
12375
  return Promise.resolve({
12283
12376
  el: cached.el,
12284
12377
  data: cached.data,