@agentscope-ai/chat 1.2.0-beta.1792117572949 → 1.2.0-beta.1792133067831

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.
@@ -27,6 +27,8 @@ import { listenForScrollIntent } from './scrollIntent';
27
27
  import {
28
28
  createTargetLayout,
29
29
  isMeasurableRect,
30
+ observeMessageLayout,
31
+ scheduleAfterLayout,
30
32
  waitForMeasurableTarget,
31
33
  } from './targetLayout';
32
34
  import type { UserMessageAnchor, UserMessageAnchorsProps } from './types';
@@ -419,6 +421,7 @@ export default function UserMessageAnchors(props: UserMessageAnchorsProps) {
419
421
  const highlightFrameRef = React.useRef<number | undefined>();
420
422
  const highlightTimeoutRef = React.useRef<number | undefined>();
421
423
  const scrollSequenceRef = React.useRef(0);
424
+ const cancelLayoutUpdateRef = React.useRef<(() => void) | undefined>();
422
425
  const cancelNavigationRef = React.useRef<
423
426
  ((measure?: boolean) => void) | undefined
424
427
  >();
@@ -590,13 +593,9 @@ export default function UserMessageAnchors(props: UserMessageAnchorsProps) {
590
593
  const scrollEl = root?.querySelector(
591
594
  `.${scrollContainerClassName}`,
592
595
  ) as HTMLElement | null;
593
- const resizeObserver =
594
- typeof ResizeObserver !== 'undefined'
595
- ? new ResizeObserver(updateAnchors)
596
- : undefined;
597
-
598
- if (scrollEl) resizeObserver?.observe(scrollEl);
599
- if (anchorTrackRef.current) resizeObserver?.observe(anchorTrackRef.current);
596
+ const disconnectResizeObserver = scrollEl
597
+ ? observeMessageLayout(scrollEl, anchorTrackRef.current, updateAnchors)
598
+ : () => {};
600
599
  const mutationObserver =
601
600
  scrollEl && typeof MutationObserver !== 'undefined'
602
601
  ? new MutationObserver(updateAnchors)
@@ -612,7 +611,7 @@ export default function UserMessageAnchors(props: UserMessageAnchorsProps) {
612
611
 
613
612
  return () => {
614
613
  cancelPendingAnchorPositionUpdate();
615
- resizeObserver?.disconnect();
614
+ disconnectResizeObserver();
616
615
  mutationObserver?.disconnect();
617
616
  scrollEl?.removeEventListener('scroll', updateAnchors);
618
617
  scrollEl?.removeEventListener('load', updateAnchors, true);
@@ -633,6 +632,8 @@ export default function UserMessageAnchors(props: UserMessageAnchorsProps) {
633
632
  const handleAnchorClick = useCallback(
634
633
  async (messageId: string) => {
635
634
  cancelNavigationRef.current?.(false);
635
+ cancelLayoutUpdateRef.current?.();
636
+ cancelLayoutUpdateRef.current = undefined;
636
637
  const root = anchorTrackRef.current?.closest(`.${prefixCls}`);
637
638
  const scrollEl = root?.querySelector(
638
639
  `.${scrollContainerClassName}`,
@@ -647,7 +648,7 @@ export default function UserMessageAnchors(props: UserMessageAnchorsProps) {
647
648
  const targetLayout = createTargetLayout();
648
649
  let highlightedTarget: HTMLElement | undefined;
649
650
  let removeScrollIntentListener = () => {};
650
- const finishNavigation = () => {
651
+ const finishNavigation = (keepActiveLock = false) => {
651
652
  removeScrollIntentListener();
652
653
  if (highlightFrameRef.current) {
653
654
  window.cancelAnimationFrame(highlightFrameRef.current);
@@ -662,7 +663,7 @@ export default function UserMessageAnchors(props: UserMessageAnchorsProps) {
662
663
  );
663
664
  targetLayout.restore();
664
665
  cancelNavigationRef.current = undefined;
665
- activeAnchorLockRef.current = undefined;
666
+ if (!keepActiveLock) activeAnchorLockRef.current = undefined;
666
667
  };
667
668
  const cancelNavigation = (measure = true) => {
668
669
  if (!isCurrent()) return;
@@ -727,8 +728,15 @@ export default function UserMessageAnchors(props: UserMessageAnchorsProps) {
727
728
  targetAtTop.classList.add(`${prefixCls}-anchor-target-active`);
728
729
  highlightTimeoutRef.current = window.setTimeout(() => {
729
730
  if (!isCurrent()) return;
730
- finishNavigation();
731
- measureAnchors();
731
+ finishNavigation(true);
732
+ // Keep the selected message through the transient intrinsic size,
733
+ // then hand active tracking back to settled geometry after paint.
734
+ cancelLayoutUpdateRef.current = scheduleAfterLayout(() => {
735
+ if (!isCurrent()) return;
736
+ cancelLayoutUpdateRef.current = undefined;
737
+ activeAnchorLockRef.current = undefined;
738
+ measureAnchors();
739
+ });
732
740
  }, TARGET_ACTIVE_DURATION);
733
741
  });
734
742
  } catch {
@@ -750,6 +758,8 @@ export default function UserMessageAnchors(props: UserMessageAnchorsProps) {
750
758
  React.useEffect(
751
759
  () => () => {
752
760
  cancelNavigationRef.current?.(false);
761
+ cancelLayoutUpdateRef.current?.();
762
+ cancelLayoutUpdateRef.current = undefined;
753
763
  scrollSequenceRef.current += 1;
754
764
  if (highlightFrameRef.current) {
755
765
  window.cancelAnimationFrame(highlightFrameRef.current);
@@ -60,3 +60,35 @@ export async function waitForMeasurableTarget(
60
60
  }
61
61
  return null;
62
62
  }
63
+
64
+ /** The scroll viewport can stay fixed while skipped bubbles regain their size. */
65
+ export function observeMessageLayout(
66
+ scrollEl: HTMLElement,
67
+ trackEl: HTMLElement | null,
68
+ onLayout: () => void,
69
+ ) {
70
+ if (typeof ResizeObserver === 'undefined') return () => {};
71
+ const observer = new ResizeObserver(onLayout);
72
+ observer.observe(scrollEl);
73
+ if (trackEl) observer.observe(trackEl);
74
+ scrollEl
75
+ .querySelectorAll<HTMLElement>('[data-role][id]')
76
+ .forEach((target) => {
77
+ observer.observe(target);
78
+ });
79
+ return () => observer.disconnect();
80
+ }
81
+
82
+ /** content-visibility's remembered intrinsic size settles after the next paint. */
83
+ export function scheduleAfterLayout(onLayout: () => void) {
84
+ let frame: number | undefined = window.requestAnimationFrame(() => {
85
+ frame = window.requestAnimationFrame(() => {
86
+ frame = undefined;
87
+ onLayout();
88
+ });
89
+ });
90
+ return () => {
91
+ if (frame !== undefined) window.cancelAnimationFrame(frame);
92
+ frame = undefined;
93
+ };
94
+ }
@@ -20,7 +20,7 @@ import React, { useCallback, useMemo, useState } from 'react';
20
20
  import { useTranslation } from "../../../Context/ChatAnywhereI18nContext";
21
21
  import { areAnchorPositionsEqual, clamp, getActiveAnchorId, getAnchorTimeText, getMessageElementInScrollContainer, getMessageElementMapInScrollContainer, getScrollVisibleTopOffset, getTargetTopOffset, getUserMessageAnchor, getUserMessageAnchorBadgeText, getUserMessageAnchorMinCount, getUserMessageAnchorMinGap, scrollTargetIntoContainerTop } from "./helpers";
22
22
  import { listenForScrollIntent } from "./scrollIntent";
23
- import { createTargetLayout, isMeasurableRect, waitForMeasurableTarget } from "./targetLayout";
23
+ import { createTargetLayout, isMeasurableRect, observeMessageLayout, scheduleAfterLayout, waitForMeasurableTarget } from "./targetLayout";
24
24
  import { jsxs as _jsxs } from "react/jsx-runtime";
25
25
  import { jsx as _jsx } from "react/jsx-runtime";
26
26
  var ANCHOR_CONTENT_GAP = 24;
@@ -382,6 +382,7 @@ export default function UserMessageAnchors(props) {
382
382
  var highlightFrameRef = React.useRef();
383
383
  var highlightTimeoutRef = React.useRef();
384
384
  var scrollSequenceRef = React.useRef(0);
385
+ var cancelLayoutUpdateRef = React.useRef();
385
386
  var cancelNavigationRef = React.useRef();
386
387
  var _useState = useState({}),
387
388
  _useState2 = _slicedToArray(_useState, 2),
@@ -527,9 +528,7 @@ export default function UserMessageAnchors(props) {
527
528
  measureAnchors();
528
529
  var root = (_anchorTrackRef$curre3 = anchorTrackRef.current) === null || _anchorTrackRef$curre3 === void 0 ? void 0 : _anchorTrackRef$curre3.closest(".".concat(prefixCls));
529
530
  var scrollEl = root === null || root === void 0 ? void 0 : root.querySelector(".".concat(scrollContainerClassName));
530
- var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(updateAnchors) : undefined;
531
- if (scrollEl) resizeObserver === null || resizeObserver === void 0 || resizeObserver.observe(scrollEl);
532
- if (anchorTrackRef.current) resizeObserver === null || resizeObserver === void 0 || resizeObserver.observe(anchorTrackRef.current);
531
+ var disconnectResizeObserver = scrollEl ? observeMessageLayout(scrollEl, anchorTrackRef.current, updateAnchors) : function () {};
533
532
  var mutationObserver = scrollEl && typeof MutationObserver !== 'undefined' ? new MutationObserver(updateAnchors) : undefined;
534
533
  mutationObserver === null || mutationObserver === void 0 || mutationObserver.observe(scrollEl, {
535
534
  childList: true,
@@ -543,7 +542,7 @@ export default function UserMessageAnchors(props) {
543
542
  window.addEventListener('resize', updateAnchors);
544
543
  return function () {
545
544
  cancelPendingAnchorPositionUpdate();
546
- resizeObserver === null || resizeObserver === void 0 || resizeObserver.disconnect();
545
+ disconnectResizeObserver();
547
546
  mutationObserver === null || mutationObserver === void 0 || mutationObserver.disconnect();
548
547
  scrollEl === null || scrollEl === void 0 || scrollEl.removeEventListener('scroll', updateAnchors);
549
548
  scrollEl === null || scrollEl === void 0 || scrollEl.removeEventListener('load', updateAnchors, true);
@@ -552,20 +551,22 @@ export default function UserMessageAnchors(props) {
552
551
  }, [anchorIdsKey, cancelPendingAnchorPositionUpdate, measureAnchors, prefixCls, renderedItemsKey, scrollContainerClassName, updateAnchors, variant, visible]);
553
552
  var handleAnchorClick = useCallback( /*#__PURE__*/function () {
554
553
  var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(messageId) {
555
- var _cancelNavigationRef$, _anchorTrackRef$curre4;
554
+ var _cancelNavigationRef$, _cancelLayoutUpdateRe, _anchorTrackRef$curre4;
556
555
  var root, scrollEl, scrollSequence, isCurrent, targetLayout, highlightedTarget, removeScrollIntentListener, finishNavigation, cancelNavigation, target, targetAtTop;
557
556
  return _regeneratorRuntime().wrap(function _callee$(_context) {
558
557
  while (1) switch (_context.prev = _context.next) {
559
558
  case 0:
560
559
  (_cancelNavigationRef$ = cancelNavigationRef.current) === null || _cancelNavigationRef$ === void 0 || _cancelNavigationRef$.call(cancelNavigationRef, false);
560
+ (_cancelLayoutUpdateRe = cancelLayoutUpdateRef.current) === null || _cancelLayoutUpdateRe === void 0 || _cancelLayoutUpdateRe.call(cancelLayoutUpdateRef);
561
+ cancelLayoutUpdateRef.current = undefined;
561
562
  root = (_anchorTrackRef$curre4 = anchorTrackRef.current) === null || _anchorTrackRef$curre4 === void 0 ? void 0 : _anchorTrackRef$curre4.closest(".".concat(prefixCls));
562
563
  scrollEl = root === null || root === void 0 ? void 0 : root.querySelector(".".concat(scrollContainerClassName));
563
564
  if (scrollEl) {
564
- _context.next = 5;
565
+ _context.next = 7;
565
566
  break;
566
567
  }
567
568
  return _context.abrupt("return");
568
- case 5:
569
+ case 7:
569
570
  scrollSequence = ++scrollSequenceRef.current;
570
571
  isCurrent = function isCurrent() {
571
572
  return scrollSequenceRef.current === scrollSequence;
@@ -576,6 +577,7 @@ export default function UserMessageAnchors(props) {
576
577
  removeScrollIntentListener = function removeScrollIntentListener() {};
577
578
  finishNavigation = function finishNavigation() {
578
579
  var _highlightedTarget;
580
+ var keepActiveLock = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
579
581
  removeScrollIntentListener();
580
582
  if (highlightFrameRef.current) {
581
583
  window.cancelAnimationFrame(highlightFrameRef.current);
@@ -588,7 +590,7 @@ export default function UserMessageAnchors(props) {
588
590
  (_highlightedTarget = highlightedTarget) === null || _highlightedTarget === void 0 || _highlightedTarget.classList.remove("".concat(prefixCls, "-anchor-target-active"));
589
591
  targetLayout.restore();
590
592
  cancelNavigationRef.current = undefined;
591
- activeAnchorLockRef.current = undefined;
593
+ if (!keepActiveLock) activeAnchorLockRef.current = undefined;
592
594
  };
593
595
  cancelNavigation = function cancelNavigation() {
594
596
  var measure = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
@@ -610,55 +612,55 @@ export default function UserMessageAnchors(props) {
610
612
  removeScrollIntentListener = listenForScrollIntent(scrollEl, function () {
611
613
  return cancelNavigation();
612
614
  });
613
- _context.prev = 15;
614
- _context.next = 18;
615
+ _context.prev = 17;
616
+ _context.next = 20;
615
617
  return onEnsureMessageVisible(messageId);
616
- case 18:
618
+ case 20:
617
619
  if (isCurrent()) {
618
- _context.next = 20;
620
+ _context.next = 22;
619
621
  break;
620
622
  }
621
623
  return _context.abrupt("return");
622
- case 20:
624
+ case 22:
623
625
  cancelPendingAnchorPositionUpdate();
624
- _context.next = 23;
626
+ _context.next = 25;
625
627
  return waitForMeasurableTarget(function () {
626
628
  return getMessageElementInScrollContainer(scrollEl, messageId);
627
629
  }, targetLayout.prepare, isCurrent, waitForNextFrame);
628
- case 23:
630
+ case 25:
629
631
  target = _context.sent;
630
632
  if (isCurrent()) {
631
- _context.next = 26;
633
+ _context.next = 28;
632
634
  break;
633
635
  }
634
636
  return _context.abrupt("return");
635
- case 26:
637
+ case 28:
636
638
  if (target) {
637
- _context.next = 30;
639
+ _context.next = 32;
638
640
  break;
639
641
  }
640
642
  finishNavigation();
641
643
  measureAnchors();
642
644
  return _context.abrupt("return");
643
- case 30:
644
- _context.next = 32;
645
- return scrollTargetIntoContainerTopAndSettle(scrollEl, messageId, target, isCurrent, targetLayout.prepare);
646
645
  case 32:
646
+ _context.next = 34;
647
+ return scrollTargetIntoContainerTopAndSettle(scrollEl, messageId, target, isCurrent, targetLayout.prepare);
648
+ case 34:
647
649
  targetAtTop = _context.sent;
648
650
  if (isCurrent()) {
649
- _context.next = 35;
651
+ _context.next = 37;
650
652
  break;
651
653
  }
652
654
  return _context.abrupt("return");
653
- case 35:
655
+ case 37:
654
656
  if (targetAtTop) {
655
- _context.next = 39;
657
+ _context.next = 41;
656
658
  break;
657
659
  }
658
660
  finishNavigation();
659
661
  measureAnchors();
660
662
  return _context.abrupt("return");
661
- case 39:
663
+ case 41:
662
664
  setActiveAnchorId(messageId);
663
665
  cancelPendingAnchorPositionUpdate();
664
666
  highlightedTarget = targetAtTop;
@@ -669,24 +671,31 @@ export default function UserMessageAnchors(props) {
669
671
  targetAtTop.classList.add("".concat(prefixCls, "-anchor-target-active"));
670
672
  highlightTimeoutRef.current = window.setTimeout(function () {
671
673
  if (!isCurrent()) return;
672
- finishNavigation();
673
- measureAnchors();
674
+ finishNavigation(true);
675
+ // Keep the selected message through the transient intrinsic size,
676
+ // then hand active tracking back to settled geometry after paint.
677
+ cancelLayoutUpdateRef.current = scheduleAfterLayout(function () {
678
+ if (!isCurrent()) return;
679
+ cancelLayoutUpdateRef.current = undefined;
680
+ activeAnchorLockRef.current = undefined;
681
+ measureAnchors();
682
+ });
674
683
  }, TARGET_ACTIVE_DURATION);
675
684
  });
676
- _context.next = 49;
685
+ _context.next = 51;
677
686
  break;
678
- case 46:
679
- _context.prev = 46;
680
- _context.t0 = _context["catch"](15);
687
+ case 48:
688
+ _context.prev = 48;
689
+ _context.t0 = _context["catch"](17);
681
690
  if (isCurrent()) {
682
691
  finishNavigation();
683
692
  measureAnchors();
684
693
  }
685
- case 49:
694
+ case 51:
686
695
  case "end":
687
696
  return _context.stop();
688
697
  }
689
- }, _callee, null, [[15, 46]]);
698
+ }, _callee, null, [[17, 48]]);
690
699
  }));
691
700
  return function (_x6) {
692
701
  return _ref3.apply(this, arguments);
@@ -694,8 +703,10 @@ export default function UserMessageAnchors(props) {
694
703
  }(), [cancelPendingAnchorPositionUpdate, measureAnchors, onEnsureMessageVisible, prefixCls, scrollContainerClassName]);
695
704
  React.useEffect(function () {
696
705
  return function () {
697
- var _cancelNavigationRef$2;
706
+ var _cancelNavigationRef$2, _cancelLayoutUpdateRe2;
698
707
  (_cancelNavigationRef$2 = cancelNavigationRef.current) === null || _cancelNavigationRef$2 === void 0 || _cancelNavigationRef$2.call(cancelNavigationRef, false);
708
+ (_cancelLayoutUpdateRe2 = cancelLayoutUpdateRef.current) === null || _cancelLayoutUpdateRe2 === void 0 || _cancelLayoutUpdateRe2.call(cancelLayoutUpdateRef);
709
+ cancelLayoutUpdateRef.current = undefined;
699
710
  scrollSequenceRef.current += 1;
700
711
  if (highlightFrameRef.current) {
701
712
  window.cancelAnimationFrame(highlightFrameRef.current);
@@ -4,3 +4,7 @@ export declare function createTargetLayout(): {
4
4
  restore(): void;
5
5
  };
6
6
  export declare function waitForMeasurableTarget(getTarget: () => HTMLElement | null, prepare: (target: HTMLElement) => void, isCurrent: () => boolean, nextFrame: () => Promise<void>): Promise<HTMLElement>;
7
+ /** The scroll viewport can stay fixed while skipped bubbles regain their size. */
8
+ export declare function observeMessageLayout(scrollEl: HTMLElement, trackEl: HTMLElement | null, onLayout: () => void): () => void;
9
+ /** content-visibility's remembered intrinsic size settles after the next paint. */
10
+ export declare function scheduleAfterLayout(onLayout: () => void): () => void;
@@ -45,6 +45,8 @@ export function createTargetLayout() {
45
45
  export function waitForMeasurableTarget(_x, _x2, _x3, _x4) {
46
46
  return _waitForMeasurableTarget.apply(this, arguments);
47
47
  }
48
+
49
+ /** The scroll viewport can stay fixed while skipped bubbles regain their size. */
48
50
  function _waitForMeasurableTarget() {
49
51
  _waitForMeasurableTarget = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(getTarget, prepare, isCurrent, nextFrame) {
50
52
  var preparedTarget, frame, _target;
@@ -99,4 +101,30 @@ function _waitForMeasurableTarget() {
99
101
  }, _callee);
100
102
  }));
101
103
  return _waitForMeasurableTarget.apply(this, arguments);
104
+ }
105
+ export function observeMessageLayout(scrollEl, trackEl, onLayout) {
106
+ if (typeof ResizeObserver === 'undefined') return function () {};
107
+ var observer = new ResizeObserver(onLayout);
108
+ observer.observe(scrollEl);
109
+ if (trackEl) observer.observe(trackEl);
110
+ scrollEl.querySelectorAll('[data-role][id]').forEach(function (target) {
111
+ observer.observe(target);
112
+ });
113
+ return function () {
114
+ return observer.disconnect();
115
+ };
116
+ }
117
+
118
+ /** content-visibility's remembered intrinsic size settles after the next paint. */
119
+ export function scheduleAfterLayout(onLayout) {
120
+ var frame = window.requestAnimationFrame(function () {
121
+ frame = window.requestAnimationFrame(function () {
122
+ frame = undefined;
123
+ onLayout();
124
+ });
125
+ });
126
+ return function () {
127
+ if (frame !== undefined) window.cancelAnimationFrame(frame);
128
+ frame = undefined;
129
+ };
102
130
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentscope-ai/chat",
3
- "version": "1.2.0-beta.1792117572949",
3
+ "version": "1.2.0-beta.1792133067831",
4
4
  "description": "a free and open-source chat framework for building excellent LLM-powered chat experiences",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": [