@nextclaw/agent-chat-ui 0.3.16 → 0.3.17

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.
package/dist/index.d.ts CHANGED
@@ -146,6 +146,7 @@ type ChatSlashMenuProps = {
146
146
  texts: Pick<ChatTexts, "slashLoadingLabel" | "slashSectionLabel" | "slashEmptyLabel" | "slashHintLabel" | "slashSkillHintLabel">;
147
147
  onSelectItem: (item: ChatSlashItem) => void;
148
148
  onOpenChange: (open: boolean) => void;
149
+ onDetailsPointerDown?: () => void;
149
150
  onSetActiveIndex: (index: number) => void;
150
151
  };
151
152
  type ChatInputBarProps = {
package/dist/index.js CHANGED
@@ -208,7 +208,7 @@ function ChatSlashMenu(props) {
208
208
  const { Popover, PopoverAnchor, PopoverContent } = ChatUiPrimitives;
209
209
  const { elementRef: anchorRef, width: panelWidth } = useElementWidth();
210
210
  const listRef = useRef(null);
211
- const { isOpen, isLoading, items, activeIndex, activeItem, texts, onSelectItem, onOpenChange, onSetActiveIndex } = props;
211
+ const { isOpen, isLoading, items, activeIndex, activeItem, texts, onSelectItem, onOpenChange, onDetailsPointerDown, onSetActiveIndex } = props;
212
212
  const resolvedWidth = useMemo(() => {
213
213
  if (!panelWidth) return;
214
214
  return Math.min(panelWidth > SLASH_PANEL_DESKTOP_MIN_WIDTH ? panelWidth * SLASH_PANEL_DESKTOP_SHRINK_RATIO : panelWidth, SLASH_PANEL_MAX_WIDTH);
@@ -276,6 +276,7 @@ function ChatSlashMenu(props) {
276
276
  })] })
277
277
  }), /* @__PURE__ */ jsx("div", {
278
278
  className: "min-w-0 p-2.5",
279
+ onPointerDown: onDetailsPointerDown,
279
280
  children: activeItem ? /* @__PURE__ */ jsxs("div", {
280
281
  className: "space-y-3",
281
282
  children: [
@@ -630,13 +631,11 @@ function ChatInputBarSkillPicker(props) {
630
631
  visibleIndex += 1;
631
632
  const isSelected = selectedSet.has(option.key);
632
633
  const isActive = index === activeIndex;
633
- return /* @__PURE__ */ jsxs("button", {
634
- type: "button",
634
+ return /* @__PURE__ */ jsxs("div", {
635
635
  id: `${listId}-option-${index}`,
636
636
  role: "option",
637
637
  "aria-selected": isSelected,
638
638
  "data-skill-index": index,
639
- onClick: () => onToggleOption(option.key),
640
639
  onMouseEnter: () => setActiveIndex(index),
641
640
  className: `flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors ${isActive ? "bg-gray-50" : "hover:bg-gray-50"}`,
642
641
  children: [
@@ -645,7 +644,7 @@ function ChatInputBarSkillPicker(props) {
645
644
  children: /* @__PURE__ */ jsx(Puzzle, { className: "h-4 w-4" })
646
645
  }),
647
646
  /* @__PURE__ */ jsxs("div", {
648
- className: "min-w-0 flex-1",
647
+ className: "min-w-0 flex-1 select-text",
649
648
  children: [/* @__PURE__ */ jsxs("div", {
650
649
  className: "flex items-center gap-1.5",
651
650
  children: [/* @__PURE__ */ jsx("span", {
@@ -662,7 +661,10 @@ function ChatInputBarSkillPicker(props) {
662
661
  }),
663
662
  /* @__PURE__ */ jsx("div", {
664
663
  className: "ml-3 shrink-0",
665
- children: /* @__PURE__ */ jsx("span", {
664
+ children: /* @__PURE__ */ jsx("button", {
665
+ type: "button",
666
+ "aria-label": `${isSelected ? "Remove" : "Add"} ${option.label}`,
667
+ onClick: () => onToggleOption(option.key),
666
668
  className: isSelected ? "inline-flex h-5 w-5 items-center justify-center rounded-full bg-primary text-white" : "inline-flex h-5 w-5 items-center justify-center rounded-full border border-gray-300 bg-white",
667
669
  children: isSelected ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) : null
668
670
  })
@@ -1879,21 +1881,19 @@ function InputBarHint({ hint }) {
1879
1881
  }
1880
1882
  const ChatInputBar = forwardRef(function ChatInputBar(props, ref) {
1881
1883
  const composerRef = useRef(null);
1882
- const [slashQuery, setSlashQuery] = useState(null);
1884
+ const isSlashMenuInteractionRef = useRef(false);
1883
1885
  const [activeSlashIndex, setActiveSlashIndex] = useState(0);
1884
1886
  const [activeSlashTriggerStart, setActiveSlashTriggerStart] = useState(null);
1885
1887
  const [dismissedSlashTriggerStart, setDismissedSlashTriggerStart] = useState(null);
1886
1888
  const isSlashPanelOpen = activeSlashTriggerStart !== null && dismissedSlashTriggerStart !== activeSlashTriggerStart;
1887
1889
  const activeSlashItem = props.slashMenu.items[activeSlashIndex] ?? null;
1890
+ const dismissSlashTrigger = () => activeSlashTriggerStart !== null && !isSlashMenuInteractionRef.current ? setDismissedSlashTriggerStart(activeSlashTriggerStart) : void 0;
1888
1891
  useEffect(() => {
1889
1892
  setActiveSlashIndex((current) => {
1890
1893
  if (props.slashMenu.items.length === 0) return 0;
1891
1894
  return Math.min(current, props.slashMenu.items.length - 1);
1892
1895
  });
1893
1896
  }, [props.slashMenu.items.length]);
1894
- useEffect(() => {
1895
- if (slashQuery !== null) setActiveSlashIndex(0);
1896
- }, [slashQuery]);
1897
1897
  useEffect(() => {
1898
1898
  if (activeSlashTriggerStart === null && dismissedSlashTriggerStart !== null) setDismissedSlashTriggerStart(null);
1899
1899
  }, [activeSlashTriggerStart, dismissedSlashTriggerStart]);
@@ -1936,14 +1936,15 @@ const ChatInputBar = forwardRef(function ChatInputBar(props, ref) {
1936
1936
  onNodesChange: props.composer.onNodesChange,
1937
1937
  onFilesAdd: props.composer.onFilesAdd,
1938
1938
  onSlashQueryChange: (query) => {
1939
- setSlashQuery(query);
1939
+ if (query === null && isSlashMenuInteractionRef.current) return;
1940
+ if (query !== null) setActiveSlashIndex(0);
1940
1941
  props.composer.onSlashQueryChange?.(query);
1941
1942
  },
1942
1943
  onSlashTriggerChange: (trigger) => {
1943
1944
  setActiveSlashTriggerStart(trigger?.start ?? null);
1944
1945
  },
1945
1946
  onSlashOpenChange: (open) => {
1946
- if (!open && activeSlashTriggerStart !== null) setDismissedSlashTriggerStart(activeSlashTriggerStart);
1947
+ if (!open) dismissSlashTrigger();
1947
1948
  },
1948
1949
  onSlashActiveIndexChange: setActiveSlashIndex
1949
1950
  }), /* @__PURE__ */ jsx(ChatSlashMenu, {
@@ -1958,7 +1959,13 @@ const ChatInputBar = forwardRef(function ChatInputBar(props, ref) {
1958
1959
  composerRef.current?.insertSlashItem(item);
1959
1960
  },
1960
1961
  onOpenChange: (open) => {
1961
- if (!open && activeSlashTriggerStart !== null) setDismissedSlashTriggerStart(activeSlashTriggerStart);
1962
+ if (!open) dismissSlashTrigger();
1963
+ },
1964
+ onDetailsPointerDown: () => {
1965
+ isSlashMenuInteractionRef.current = true;
1966
+ requestAnimationFrame(() => {
1967
+ isSlashMenuInteractionRef.current = false;
1968
+ });
1962
1969
  },
1963
1970
  onSetActiveIndex: setActiveSlashIndex
1964
1971
  })]
@@ -3785,6 +3792,48 @@ function ChatMessageActionCopy({ message, texts }) {
3785
3792
  //#endregion
3786
3793
  //#region src/components/chat/ui/chat-message-list/chat-message-list.tsx
3787
3794
  const INVISIBLE_ONLY_TEXT_PATTERN = /\u200B|\u200C|\u200D|\u2060|\uFEFF/g;
3795
+ const TYPING_TEXT_SHEEN_CSS = `
3796
+ @keyframes nextclaw-chat-typing-text-sheen {
3797
+ 0% {
3798
+ background-position: 160% 0;
3799
+ }
3800
+ 64% {
3801
+ background-position: -60% 0;
3802
+ }
3803
+ 100% {
3804
+ background-position: -60% 0;
3805
+ }
3806
+ }
3807
+
3808
+ .nextclaw-chat-typing-indicator__text {
3809
+ background-image: linear-gradient(
3810
+ 100deg,
3811
+ #6b7280 0%,
3812
+ #6b7280 34%,
3813
+ #a6adba 43%,
3814
+ #f8fafc 50%,
3815
+ #a6adba 57%,
3816
+ #6b7280 66%,
3817
+ #6b7280 100%
3818
+ );
3819
+ background-size: 240% 100%;
3820
+ background-position: 160% 0;
3821
+ -webkit-background-clip: text;
3822
+ background-clip: text;
3823
+ color: transparent;
3824
+ -webkit-text-fill-color: transparent;
3825
+ animation: nextclaw-chat-typing-text-sheen 4.2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
3826
+ }
3827
+
3828
+ @media (prefers-reduced-motion: reduce) {
3829
+ .nextclaw-chat-typing-indicator__text {
3830
+ animation: none;
3831
+ background-image: none;
3832
+ color: #6b7280;
3833
+ -webkit-text-fill-color: currentColor;
3834
+ }
3835
+ }
3836
+ `;
3788
3837
  function hasRenderableText(value) {
3789
3838
  const trimmed = value.trim();
3790
3839
  if (!trimmed) return false;
@@ -3809,6 +3858,15 @@ function ChatMessageTypingFooter() {
3809
3858
  })
3810
3859
  });
3811
3860
  }
3861
+ function ChatTypingIndicator({ label }) {
3862
+ return /* @__PURE__ */ jsxs("div", {
3863
+ className: "rounded-2xl border border-gray-200 bg-white px-4 py-3 text-sm text-gray-500 shadow-sm",
3864
+ children: [/* @__PURE__ */ jsx("style", { children: TYPING_TEXT_SHEEN_CSS }), /* @__PURE__ */ jsx("span", {
3865
+ className: "nextclaw-chat-typing-indicator__text",
3866
+ children: label
3867
+ })]
3868
+ });
3869
+ }
3812
3870
  function ChatMessageList(props) {
3813
3871
  const visibleMessages = props.messages.filter(hasRenderableMessageContent);
3814
3872
  const hasRenderableAssistantDraft = visibleMessages.some((message) => message.role === "assistant" && (message.status === "streaming" || message.status === "pending"));
@@ -3846,10 +3904,7 @@ function ChatMessageList(props) {
3846
3904
  }, message.id);
3847
3905
  }), props.isSending && !hasRenderableAssistantDraft ? /* @__PURE__ */ jsxs("div", {
3848
3906
  className: "flex justify-start gap-3",
3849
- children: [/* @__PURE__ */ jsx(ChatMessageAvatar, { role: "assistant" }), /* @__PURE__ */ jsx("div", {
3850
- className: "rounded-2xl border border-gray-200 bg-white px-4 py-3 text-sm text-gray-500 shadow-sm",
3851
- children: props.texts.typingLabel
3852
- })]
3907
+ children: [/* @__PURE__ */ jsx(ChatMessageAvatar, { role: "assistant" }), /* @__PURE__ */ jsx(ChatTypingIndicator, { label: props.texts.typingLabel })]
3853
3908
  }) : null]
3854
3909
  });
3855
3910
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/agent-chat-ui",
3
- "version": "0.3.16",
3
+ "version": "0.3.17",
4
4
  "private": false,
5
5
  "description": "Reusable Nextclaw agent chat UI primitives and default skin.",
6
6
  "type": "module",