@refraction-ui/react 0.10.0 → 0.11.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.
package/dist/index.js CHANGED
@@ -2731,6 +2731,7 @@ function createConversation(config = {}) {
2731
2731
  const messagesByConv = /* @__PURE__ */ new Map();
2732
2732
  let activeConversationId = config.activeConversationId ?? null;
2733
2733
  let openThreadRootId = null;
2734
+ let replyTarget = null;
2734
2735
  let status = "idle";
2735
2736
  let error = null;
2736
2737
  let abortController = null;
@@ -2752,6 +2753,7 @@ function createConversation(config = {}) {
2752
2753
  activeConversationId,
2753
2754
  messages: activeConversationId ? messagesByConv.get(activeConversationId) ?? [] : [],
2754
2755
  openThreadRootId,
2756
+ replyTarget,
2755
2757
  threadingMode,
2756
2758
  status,
2757
2759
  error
@@ -2848,6 +2850,7 @@ function createConversation(config = {}) {
2848
2850
  newConversation(opts) {
2849
2851
  const conversation = createConversationInternal(opts ?? {});
2850
2852
  openThreadRootId = null;
2853
+ replyTarget = null;
2851
2854
  emit();
2852
2855
  return conversation;
2853
2856
  },
@@ -2855,6 +2858,7 @@ function createConversation(config = {}) {
2855
2858
  if (!conversations.has(conversationId) || activeConversationId === conversationId) return;
2856
2859
  activeConversationId = conversationId;
2857
2860
  openThreadRootId = null;
2861
+ replyTarget = null;
2858
2862
  emit();
2859
2863
  },
2860
2864
  deleteConversation(conversationId) {
@@ -2864,6 +2868,7 @@ function createConversation(config = {}) {
2864
2868
  if (activeConversationId === conversationId) {
2865
2869
  activeConversationId = orderedConversations()[0]?.id ?? null;
2866
2870
  openThreadRootId = null;
2871
+ replyTarget = null;
2867
2872
  }
2868
2873
  emit();
2869
2874
  },
@@ -2901,6 +2906,7 @@ function createConversation(config = {}) {
2901
2906
  const next = list.filter((m) => !removeIds.has(m.id));
2902
2907
  messagesByConv.set(activeConversationId, next);
2903
2908
  if (openThreadRootId && removeIds.has(openThreadRootId)) openThreadRootId = null;
2909
+ if (replyTarget && removeIds.has(replyTarget)) replyTarget = openThreadRootId;
2904
2910
  emit();
2905
2911
  },
2906
2912
  react(messageId, emoji) {
@@ -2931,6 +2937,7 @@ function createConversation(config = {}) {
2931
2937
  const conversationId = ensureActiveConversation(opts);
2932
2938
  const list = messagesByConv.get(conversationId);
2933
2939
  const parentId = opts?.replyTo ? rootIdOf(list, opts.replyTo) : void 0;
2940
+ const replyToId = opts?.replyTo;
2934
2941
  const isFirstRoot = !parentId && selectRoots(list).length === 0;
2935
2942
  const userMsg = {
2936
2943
  id: generateId("rfr-msg"),
@@ -2941,6 +2948,7 @@ function createConversation(config = {}) {
2941
2948
  timestamp: /* @__PURE__ */ new Date(),
2942
2949
  status: "sent",
2943
2950
  parentId,
2951
+ replyToId,
2944
2952
  attachments: opts?.attachments,
2945
2953
  metadata: opts?.metadata
2946
2954
  };
@@ -2997,11 +3005,20 @@ function createConversation(config = {}) {
2997
3005
  },
2998
3006
  openThread(rootId) {
2999
3007
  openThreadRootId = rootId;
3008
+ replyTarget = rootId;
3009
+ emit();
3010
+ },
3011
+ replyTo(messageId) {
3012
+ if (!activeConversationId) return;
3013
+ const list = messagesByConv.get(activeConversationId);
3014
+ openThreadRootId = rootIdOf(list, messageId);
3015
+ replyTarget = messageId;
3000
3016
  emit();
3001
3017
  },
3002
3018
  closeThread() {
3003
- if (openThreadRootId === null) return;
3019
+ if (openThreadRootId === null && replyTarget === null) return;
3004
3020
  openThreadRootId = null;
3021
+ replyTarget = null;
3005
3022
  emit();
3006
3023
  },
3007
3024
  setThreadingMode(mode) {
@@ -3345,6 +3362,7 @@ function useConversation(config) {
3345
3362
  retryLast: api.retryLast,
3346
3363
  stop: api.stop,
3347
3364
  openThread: api.openThread,
3365
+ replyTo: api.replyTo,
3348
3366
  closeThread: api.closeThread,
3349
3367
  setThreadingMode: api.setThreadingMode
3350
3368
  };
@@ -3807,7 +3825,7 @@ function HoverActions({
3807
3825
  onToggleEmojis,
3808
3826
  align
3809
3827
  }) {
3810
- const { state, openThread, deleteMessage } = conversation;
3828
+ const { replyTo, deleteMessage } = conversation;
3811
3829
  return h2(
3812
3830
  "div",
3813
3831
  {
@@ -3816,7 +3834,8 @@ function HoverActions({
3816
3834
  align === "end" && "justify-end"
3817
3835
  )
3818
3836
  },
3819
- h2("button", { type: "button", className: "hover:text-foreground", onClick: () => openThread(rootIdOf(state.messages, message.id)) }, "Reply"),
3837
+ // Reply targets this specific message but groups under the originating root.
3838
+ h2("button", { type: "button", className: "hover:text-foreground", onClick: () => replyTo(message.id) }, "Reply"),
3820
3839
  h2("button", { type: "button", className: "hover:text-foreground", onClick: onToggleEmojis }, "React"),
3821
3840
  isOwn ? h2("button", { type: "button", className: "hover:text-foreground", onClick: onEdit }, "Edit") : null,
3822
3841
  isOwn ? h2("button", { type: "button", className: "hover:text-destructive", onClick: () => deleteMessage(message.id) }, "Delete") : null
@@ -3878,7 +3897,7 @@ function MessageRow({
3878
3897
  const inner = h2(
3879
3898
  React11.Fragment,
3880
3899
  null,
3881
- quotedParent ? h2(QuotedParent, { parent: quotedParent, onClick: () => openThread(quotedParent.id) }) : null,
3900
+ quotedParent ? h2(QuotedParent, { parent: quotedParent, onClick: () => openThread(rootIdOf(state.messages, quotedParent.id)) }) : null,
3882
3901
  editing ? h2(EditField, { message, conversation, onDone: () => setEditing(false) }) : isUser ? h2("div", { className: "inline-block rounded-2xl rounded-br-sm bg-primary/10 px-3 py-2 text-left" }, h2(MessageBody, { message })) : h2(MessageBody, { message }),
3883
3902
  h2(Reactions, { message, onReact: (e) => react(message.id, e), align }),
3884
3903
  showThreadAffordance && replyCount > 0 ? h2(
@@ -3978,13 +3997,14 @@ function ThreadPanel({ conversation, currentUserId, composer }) {
3978
3997
  const rootId = state.openThreadRootId;
3979
3998
  if (!rootId) return null;
3980
3999
  const messages = selectThreadMessages(state.messages, rootId);
4000
+ const target = state.replyTarget && state.replyTarget !== rootId ? findMessage(state.messages, state.replyTarget) : void 0;
3981
4001
  return h2(
3982
4002
  "aside",
3983
4003
  { className: "flex w-80 flex-col border-l border-border", "aria-label": "Thread" },
3984
4004
  h2(
3985
4005
  "div",
3986
4006
  { className: "flex items-center justify-between border-b border-border px-3 py-2" },
3987
- h2("span", { className: "text-sm font-semibold" }, "Thread"),
4007
+ h2("span", { className: "text-sm font-semibold" }, `Thread \xB7 ${messages.length - 1} ${messages.length - 1 === 1 ? "reply" : "replies"}`),
3988
4008
  h2("button", { type: "button", className: "text-muted-foreground hover:text-foreground", "aria-label": "Close thread", onClick: () => conversation.closeThread() }, "\u2715")
3989
4009
  ),
3990
4010
  h2(
@@ -3992,6 +4012,12 @@ function ThreadPanel({ conversation, currentUserId, composer }) {
3992
4012
  { className: "flex-1 overflow-y-auto p-1" },
3993
4013
  ...messages.map((m) => h2(MessageRow, { key: m.id, message: m, conversation, currentUserId, showThreadAffordance: false }))
3994
4014
  ),
4015
+ target ? h2(
4016
+ "div",
4017
+ { className: "flex items-center justify-between gap-2 border-t border-border bg-muted/40 px-3 py-1 text-xs text-muted-foreground" },
4018
+ h2("span", { className: "truncate" }, `\u21B3 Replying to ${target.author.name}`),
4019
+ h2("button", { type: "button", className: "hover:text-foreground", onClick: () => conversation.openThread(rootId) }, "Reply to thread instead")
4020
+ ) : null,
3995
4021
  composer
3996
4022
  );
3997
4023
  }
@@ -4047,7 +4073,7 @@ function Chat({
4047
4073
  mentions,
4048
4074
  onSlashCommand,
4049
4075
  toolbar: composerToolbar,
4050
- onSubmit: (content, atts) => void sendMessage(content, { replyTo: state.openThreadRootId, attachments: atts }),
4076
+ onSubmit: (content, atts) => void sendMessage(content, { replyTo: state.replyTarget ?? state.openThreadRootId, attachments: atts }),
4051
4077
  onStop: () => conversation.stop()
4052
4078
  }) : null;
4053
4079
  const body = timeline.length === 0 ? h2("div", { className: "flex flex-1 items-center justify-center p-6 text-sm text-muted-foreground" }, emptyState ?? "No messages yet. Say hello \u{1F44B}") : h2(
@@ -4059,8 +4085,10 @@ function Chat({
4059
4085
  message: m,
4060
4086
  conversation,
4061
4087
  currentUserId,
4062
- showThreadAffordance: state.threadingMode === "panel",
4063
- quotedParent: state.threadingMode === "inline" && m.parentId ? findMessage(state.messages, m.parentId) : void 0
4088
+ // Show the "N replies" count on originating messages in BOTH modes.
4089
+ showThreadAffordance: true,
4090
+ // Inline: quote the specific message replied to (falls back to the root).
4091
+ quotedParent: state.threadingMode === "inline" && m.parentId ? findMessage(state.messages, m.replyToId ?? m.parentId) : void 0
4064
4092
  })
4065
4093
  )
4066
4094
  );