@assistant-ui/react-native 0.1.44 → 0.1.46

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.
Files changed (89) hide show
  1. package/LICENSE +1 -1
  2. package/dist/index.d.ts +4 -3
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +3 -3
  5. package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
  6. package/dist/primitives/actionBar/ActionBarEdit.js.map +1 -1
  7. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
  8. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
  9. package/dist/primitives/actionBar/ActionBarReload.js.map +1 -1
  10. package/dist/primitives/actionBar/ActionBarSpeak.js.map +1 -1
  11. package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -1
  12. package/dist/primitives/attachment/AttachmentName.js.map +1 -1
  13. package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
  14. package/dist/primitives/attachment/AttachmentRoot.js.map +1 -1
  15. package/dist/primitives/attachment/AttachmentThumb.js.map +1 -1
  16. package/dist/primitives/branchPicker/BranchPickerCount.js.map +1 -1
  17. package/dist/primitives/branchPicker/BranchPickerNext.js.map +1 -1
  18. package/dist/primitives/branchPicker/BranchPickerNumber.js.map +1 -1
  19. package/dist/primitives/branchPicker/BranchPickerPrevious.js.map +1 -1
  20. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
  21. package/dist/primitives/chainOfThought/ChainOfThoughtRoot.js.map +1 -1
  22. package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
  23. package/dist/primitives/composer/ComposerCancel.js.map +1 -1
  24. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  25. package/dist/primitives/composer/ComposerQuote.js.map +1 -1
  26. package/dist/primitives/composer/ComposerQuoteDismiss.js.map +1 -1
  27. package/dist/primitives/composer/ComposerQuoteText.js.map +1 -1
  28. package/dist/primitives/composer/ComposerRoot.js.map +1 -1
  29. package/dist/primitives/composer/ComposerSend.js.map +1 -1
  30. package/dist/primitives/error/ErrorMessage.js.map +1 -1
  31. package/dist/primitives/error/ErrorRoot.js.map +1 -1
  32. package/dist/primitives/message/MessageContent.d.ts.map +1 -1
  33. package/dist/primitives/message/MessageContent.js +43 -33
  34. package/dist/primitives/message/MessageContent.js.map +1 -1
  35. package/dist/primitives/message/MessageIf.js.map +1 -1
  36. package/dist/primitives/message/MessageParts.js.map +1 -1
  37. package/dist/primitives/message/MessageRoot.js.map +1 -1
  38. package/dist/primitives/message.d.ts +1 -0
  39. package/dist/primitives/message.d.ts.map +1 -1
  40. package/dist/primitives/message.js +3 -2
  41. package/dist/primitives/message.js.map +1 -1
  42. package/dist/primitives/messagePart/MessagePartImage.js.map +1 -1
  43. package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
  44. package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
  45. package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
  46. package/dist/primitives/queueItem/QueueItemText.js.map +1 -1
  47. package/dist/primitives/suggestion/SuggestionDescription.js.map +1 -1
  48. package/dist/primitives/suggestion/SuggestionTitle.js.map +1 -1
  49. package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
  50. package/dist/primitives/thread/ThreadEmpty.js.map +1 -1
  51. package/dist/primitives/thread/ThreadIf.js.map +1 -1
  52. package/dist/primitives/thread/ThreadMessages.d.ts +7 -5
  53. package/dist/primitives/thread/ThreadMessages.d.ts.map +1 -1
  54. package/dist/primitives/thread/ThreadMessages.js +109 -376
  55. package/dist/primitives/thread/ThreadMessages.js.map +1 -1
  56. package/dist/primitives/thread/ThreadRoot.js.map +1 -1
  57. package/dist/primitives/thread/ThreadRowsFlatList.d.ts +29 -0
  58. package/dist/primitives/thread/ThreadRowsFlatList.d.ts.map +1 -0
  59. package/dist/primitives/thread/ThreadRowsFlatList.js +226 -0
  60. package/dist/primitives/thread/ThreadRowsFlatList.js.map +1 -0
  61. package/dist/primitives/thread/ThreadSuggestion.js.map +1 -1
  62. package/dist/primitives/thread/flatListScroll.d.ts +52 -0
  63. package/dist/primitives/thread/flatListScroll.d.ts.map +1 -0
  64. package/dist/primitives/thread/flatListScroll.js +380 -0
  65. package/dist/primitives/thread/flatListScroll.js.map +1 -0
  66. package/dist/primitives/thread.d.ts +2 -0
  67. package/dist/primitives/thread.d.ts.map +1 -1
  68. package/dist/primitives/thread.js +5 -2
  69. package/dist/primitives/thread.js.map +1 -1
  70. package/dist/primitives/threadList/ThreadListItems.js.map +1 -1
  71. package/dist/primitives/threadList/ThreadListNew.js.map +1 -1
  72. package/dist/primitives/threadList/ThreadListRoot.js.map +1 -1
  73. package/dist/primitives/threadListItem/ThreadListItemArchive.js.map +1 -1
  74. package/dist/primitives/threadListItem/ThreadListItemDelete.js.map +1 -1
  75. package/dist/primitives/threadListItem/ThreadListItemRoot.js.map +1 -1
  76. package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
  77. package/dist/primitives/threadListItem/ThreadListItemUnarchive.js.map +1 -1
  78. package/package.json +13 -9
  79. package/src/index.ts +18 -1
  80. package/src/primitives/message/MessageContent.test.tsx +46 -1
  81. package/src/primitives/message/MessageContent.tsx +3 -1
  82. package/src/primitives/message.test.tsx +74 -0
  83. package/src/primitives/message.ts +1 -0
  84. package/src/primitives/thread/ThreadMessages.test.tsx +28 -1
  85. package/src/primitives/thread/ThreadMessages.tsx +49 -343
  86. package/src/primitives/thread/ThreadRowsFlatList.test.tsx +253 -0
  87. package/src/primitives/thread/ThreadRowsFlatList.tsx +159 -0
  88. package/src/primitives/thread/flatListScroll.ts +467 -0
  89. package/src/primitives/thread.ts +5 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react-native",
3
- "version": "0.1.44",
3
+ "version": "0.1.46",
4
4
  "description": "React Native bindings for assistant-ui",
5
5
  "keywords": [
6
6
  "assistant",
@@ -31,11 +31,11 @@
31
31
  ],
32
32
  "sideEffects": false,
33
33
  "dependencies": {
34
- "@assistant-ui/core": "^0.3.21",
35
- "@assistant-ui/store": "^0.3.15",
36
- "@assistant-ui/tap": "^0.9.19",
37
- "assistant-cloud": "^0.2.3",
38
- "assistant-stream": "^0.3.45"
34
+ "@assistant-ui/core": "^0.3.24",
35
+ "@assistant-ui/store": "^0.3.17",
36
+ "@assistant-ui/tap": "^0.9.21",
37
+ "assistant-cloud": "^0.2.5",
38
+ "assistant-stream": "^0.3.48"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@types/react": "*",
@@ -48,7 +48,8 @@
48
48
  }
49
49
  },
50
50
  "devDependencies": {
51
- "@assistant-ui/x-buildutils": "0.0.29",
51
+ "@assistant-ui/x-buildutils": "0.0.30",
52
+ "@assistant-ui/x-react18": "0.0.0",
52
53
  "@babel/core": "^7.29.7",
53
54
  "@types/react": "^19.3.0",
54
55
  "@types/react-dom": "^19.3.0",
@@ -56,8 +57,8 @@
56
57
  "react": "^19.3.0",
57
58
  "react-dom": "^19.3.0",
58
59
  "react-native": "^0.87.1",
59
- "react-native-web": "^0.21.2",
60
- "vitest": "^5.0.1"
60
+ "react-native-web": "^0.21.3",
61
+ "vitest": "^5.0.3"
61
62
  },
62
63
  "publishConfig": {
63
64
  "access": "public",
@@ -75,7 +76,10 @@
75
76
  "scripts": {
76
77
  "build": "aui-build",
77
78
  "test": "vitest run",
79
+ "test:coverage": "vitest run --coverage --coverage.reporter=text --coverage.reporter=html --coverage.reporter=json-summary",
78
80
  "typecheck": "tsc --noEmit",
81
+ "test:peer-react18": "vitest run --config vitest.peer-react18.config.ts",
82
+ "test:types:peer-react18": "aui-react18-types",
79
83
  "test:watch": "vitest"
80
84
  }
81
85
  }
package/src/index.ts CHANGED
@@ -20,9 +20,13 @@ export type {
20
20
  ToolApprovalDisplay,
21
21
  ToolApprovalOption,
22
22
  ToolApprovalOptionKind,
23
+ ToolApprovalAnswer,
24
+ ToolApprovalQuestion,
25
+ ToolApprovalQuestionOption,
23
26
  ToolApprovalResponse,
24
27
  ToolCallMessagePart,
25
28
  ToolCallMessagePartStatus,
29
+ MessagePartTiming,
26
30
  ToolCallTiming,
27
31
  ToolModelContentPart,
28
32
  ImageMessagePart,
@@ -179,6 +183,11 @@ export * as SuggestionPrimitive from "./primitives/suggestion";
179
183
  export * as ErrorPrimitive from "./primitives/error";
180
184
 
181
185
  export { groupPartByType, type GroupByContext } from "@assistant-ui/core/react";
186
+ export {
187
+ createThreadRowsSelector,
188
+ type ThreadRow,
189
+ type ThreadRowsOptions,
190
+ } from "@assistant-ui/core/react";
182
191
  export { unstable_useThreadMessageIds } from "@assistant-ui/core/react";
183
192
 
184
193
  // Re-export shared providers from core/react
@@ -404,9 +413,17 @@ export type {
404
413
  ThreadSuggestion,
405
414
  ToolCallMessagePartMcpMetadata,
406
415
  ToolExecutionStatus,
416
+ DirectiveFormatter,
417
+ DirectiveSegment,
418
+ TriggerAdapter,
419
+ TriggerCategory,
420
+ TriggerItem,
407
421
  Unstable_DirectiveFormatter,
408
422
  Unstable_DirectiveSegment,
409
423
  Unstable_TriggerItem,
410
424
  VoiceSessionState,
411
425
  } from "@assistant-ui/core";
412
- export { unstable_defaultDirectiveFormatter } from "@assistant-ui/core";
426
+ export {
427
+ defaultDirectiveFormatter,
428
+ unstable_defaultDirectiveFormatter,
429
+ } from "@assistant-ui/core";
@@ -1,4 +1,4 @@
1
- import { act, type ReactElement } from "react";
1
+ import { act, useState, type ReactElement } from "react";
2
2
  import { createRoot, type Root } from "react-dom/client";
3
3
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
4
  import { MessageContent } from "./MessageContent";
@@ -79,6 +79,51 @@ describe("MessageContent", () => {
79
79
  });
80
80
  };
81
81
 
82
+ it("keeps a text seed while streaming and resets it for a replacement id", async () => {
83
+ const SeededText = ({ text }: { text: string }) => {
84
+ const [seed] = useState(text);
85
+ return <span>{`${seed}:${text}`}</span>;
86
+ };
87
+ const renderText: NonNullable<
88
+ Parameters<typeof MessageContent>[0]["renderText"]
89
+ > = ({ part }) => <SeededText text={part.text} />;
90
+
91
+ h.state.message.content = [{ type: "text", id: "p1", text: "old" }];
92
+ await mount({ renderText });
93
+ h.state.message.content = [
94
+ { type: "text", id: "p1", text: "old streamed" },
95
+ ];
96
+ await mount({ renderText });
97
+ expect(container.textContent).toBe("old:old streamed");
98
+
99
+ h.state.message.content = [{ type: "text", id: "p2", text: "new" }];
100
+ await mount({ renderText });
101
+ expect(container.textContent).toBe("new:new");
102
+ });
103
+
104
+ it("keeps each seed with its id when identified parts swap", async () => {
105
+ const SeededText = ({ text }: { text: string }) => {
106
+ const [seed] = useState(text);
107
+ return <span>{`${seed}:${text}`}</span>;
108
+ };
109
+ const renderText: NonNullable<
110
+ Parameters<typeof MessageContent>[0]["renderText"]
111
+ > = ({ part }) => <SeededText text={part.text} />;
112
+ h.state.message.content = [
113
+ { type: "text", id: "p1", text: "first" },
114
+ { type: "text", id: "p2", text: "second" },
115
+ ];
116
+ await mount({ renderText });
117
+ h.state.message.content = [
118
+ { type: "text", id: "p2", text: "second updated" },
119
+ { type: "text", id: "p1", text: "first updated" },
120
+ ];
121
+ await mount({ renderText });
122
+ expect(
123
+ Array.from(container.querySelectorAll("span"), (el) => el.textContent),
124
+ ).toEqual(["second:second updated", "first:first updated"]);
125
+ });
126
+
82
127
  it("renders a text part through the default text renderer", async () => {
83
128
  h.state.message.content = [{ type: "text", text: "hello world" }];
84
129
  await mount();
@@ -1,5 +1,6 @@
1
1
  import { type ReactElement, Fragment, useMemo } from "react";
2
2
  import { Text } from "react-native";
3
+ import { getMessagePartKeys } from "@assistant-ui/core/internal";
3
4
  import type {
4
5
  ThreadUserMessagePart,
5
6
  ThreadAssistantMessagePart,
@@ -124,11 +125,12 @@ export const MessageContent = ({
124
125
  renderData,
125
126
  }: MessageContentProps) => {
126
127
  const content = useAuiState((s) => s.message.parts);
128
+ const partKeys = getMessagePartKeys(content);
127
129
 
128
130
  return (
129
131
  <>
130
132
  {content.map((part, index) => {
131
- const key = `${part.type}-${index}`;
133
+ const key = partKeys[index];
132
134
  switch (part.type) {
133
135
  case "text":
134
136
  return (
@@ -0,0 +1,74 @@
1
+ import { act, type ReactElement } from "react";
2
+ import { createRoot, type Root } from "react-dom/client";
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { Text } from "react-native";
5
+ import * as MessagePrimitive from "./message";
6
+
7
+ const h = vi.hoisted(() => ({
8
+ message: { metadata: { custom: {} } as { custom: Record<string, unknown> } },
9
+ }));
10
+
11
+ vi.mock("@assistant-ui/store", async (importOriginal) => {
12
+ const actual = await importOriginal<typeof import("@assistant-ui/store")>();
13
+ return {
14
+ ...actual,
15
+ useAuiState: <T,>(selector: (state: typeof h) => T) => selector(h),
16
+ };
17
+ });
18
+
19
+ (globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
20
+
21
+ describe("MessagePrimitive.Quote", () => {
22
+ let container: HTMLDivElement;
23
+ let root: Root;
24
+
25
+ beforeEach(() => {
26
+ h.message = { metadata: { custom: {} } };
27
+ container = document.createElement("div");
28
+ document.body.appendChild(container);
29
+ root = createRoot(container);
30
+ });
31
+
32
+ afterEach(async () => {
33
+ await act(async () => {
34
+ root.unmount();
35
+ });
36
+ container.remove();
37
+ });
38
+
39
+ const render = async (element: ReactElement) => {
40
+ await act(async () => {
41
+ root.render(element);
42
+ });
43
+ };
44
+
45
+ it("renders nothing for a message without a quote", async () => {
46
+ await render(
47
+ <MessagePrimitive.Quote>
48
+ {({ text }) => <Text testID="quote">{text}</Text>}
49
+ </MessagePrimitive.Quote>,
50
+ );
51
+
52
+ expect(container.querySelector('[data-testid="quote"]')).toBeNull();
53
+ });
54
+
55
+ it("passes the quote text and source message id to its child", async () => {
56
+ h.message = {
57
+ metadata: { custom: { quote: { text: "quoted text", messageId: "m1" } } },
58
+ };
59
+
60
+ await render(
61
+ <MessagePrimitive.Quote>
62
+ {({ text, messageId }) => (
63
+ <Text testID="quote">
64
+ {messageId}: {text}
65
+ </Text>
66
+ )}
67
+ </MessagePrimitive.Quote>,
68
+ );
69
+
70
+ expect(container.querySelector('[data-testid="quote"]')?.textContent).toBe(
71
+ "m1: quoted text",
72
+ );
73
+ });
74
+ });
@@ -11,6 +11,7 @@ export {
11
11
  MessagePrimitivePartByIndex as PartByIndex,
12
12
  } from "./message/MessageParts";
13
13
  export { MessagePrimitiveGroupedParts as GroupedParts } from "@assistant-ui/core/react";
14
+ export { MessagePrimitiveQuote as Quote } from "@assistant-ui/core/react";
14
15
  export {
15
16
  MessageIf as If,
16
17
  type MessageIfProps as IfProps,
@@ -10,13 +10,18 @@ type Msg = { id: string; role: string };
10
10
 
11
11
  const h = vi.hoisted(() => ({
12
12
  state: {
13
- thread: { messages: [] as Msg[] },
13
+ thread: {
14
+ messages: [] as Msg[],
15
+ hasEarlier: false,
16
+ isLoadingEarlier: false,
17
+ },
14
18
  message: { role: "user" as string, composer: { isEditing: false } },
15
19
  },
16
20
  itemState: { role: "user" } as { role: string; parts?: unknown[] },
17
21
  events: {} as Record<string, Set<() => void>>,
18
22
  flatListProps: null as Record<string, unknown> | null,
19
23
  scrollToOffset: vi.fn(),
24
+ loadEarlier: vi.fn(async () => {}),
20
25
  }));
21
26
 
22
27
  vi.mock("react-native", async (importOriginal) => {
@@ -79,7 +84,9 @@ vi.mock("react-native", async (importOriginal) => {
79
84
  vi.mock("@assistant-ui/store", async () => {
80
85
  const React = await import("react");
81
86
 
87
+ const aui = { thread: { loadEarlier: h.loadEarlier } };
82
88
  return {
89
+ useAui: () => aui,
83
90
  useAuiState: <T,>(selector: (s: typeof h.state) => T) => selector(h.state),
84
91
  useAuiEvent: (
85
92
  selector: string | { scope: string; event: string },
@@ -400,6 +407,26 @@ describe("ThreadMessages", () => {
400
407
  });
401
408
 
402
409
  describe("MessagesFlatList history", () => {
410
+ it("pages through the runtime when no history prop is given", async () => {
411
+ h.state.thread.hasEarlier = true;
412
+ try {
413
+ await mountFlatList({ components: messageComponents });
414
+ getFlatListProps().onStartReached?.({ distanceFromStart: 0 });
415
+ expect(h.loadEarlier).toHaveBeenCalledOnce();
416
+
417
+ h.state.thread.isLoadingEarlier = true;
418
+ await mountFlatList({ components: messageComponents });
419
+ expect(getFlatListProps().onStartReached).toBeUndefined();
420
+ } finally {
421
+ h.state.thread.hasEarlier = false;
422
+ h.state.thread.isLoadingEarlier = false;
423
+ }
424
+
425
+ await mountFlatList({ components: messageComponents });
426
+ expect(getFlatListProps().onStartReached).toBeUndefined();
427
+ expect(getFlatListProps().onStartReachedThreshold).toBeUndefined();
428
+ });
429
+
403
430
  it("wires loadMore only while history can load more", async () => {
404
431
  const loadMore = vi.fn();
405
432