@assistant-ui/react-native 0.1.42 → 0.1.44

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 (228) hide show
  1. package/dist/context/AssistantContext.d.ts +2 -2
  2. package/dist/context/AssistantContext.d.ts.map +1 -0
  3. package/dist/context/providers/AttachmentByIndexProvider.d.ts +2 -2
  4. package/dist/context/providers/AttachmentByIndexProvider.d.ts.map +1 -0
  5. package/dist/context/providers/RuntimeAdapterProvider.d.ts +2 -2
  6. package/dist/context/providers/RuntimeAdapterProvider.d.ts.map +1 -0
  7. package/dist/context/providers/ThreadListItemRuntimeProvider.d.ts +2 -2
  8. package/dist/context/providers/ThreadListItemRuntimeProvider.d.ts.map +1 -0
  9. package/dist/index.d.ts +54 -22
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +5 -4
  12. package/dist/internal.d.ts +4 -3
  13. package/dist/internal.d.ts.map +1 -0
  14. package/dist/primitives/actionBar/ActionBarCopy.d.ts +9 -11
  15. package/dist/primitives/actionBar/ActionBarCopy.d.ts.map +1 -1
  16. package/dist/primitives/actionBar/ActionBarCopy.js +23 -19
  17. package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
  18. package/dist/primitives/actionBar/ActionBarEdit.d.ts +7 -7
  19. package/dist/primitives/actionBar/ActionBarEdit.d.ts.map +1 -1
  20. package/dist/primitives/actionBar/ActionBarEdit.js +21 -11
  21. package/dist/primitives/actionBar/ActionBarEdit.js.map +1 -1
  22. package/dist/primitives/actionBar/ActionBarFeedbackNegative.d.ts +8 -10
  23. package/dist/primitives/actionBar/ActionBarFeedbackNegative.d.ts.map +1 -1
  24. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +35 -17
  25. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
  26. package/dist/primitives/actionBar/ActionBarFeedbackPositive.d.ts +8 -10
  27. package/dist/primitives/actionBar/ActionBarFeedbackPositive.d.ts.map +1 -1
  28. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +35 -17
  29. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
  30. package/dist/primitives/actionBar/ActionBarReload.d.ts +7 -7
  31. package/dist/primitives/actionBar/ActionBarReload.d.ts.map +1 -1
  32. package/dist/primitives/actionBar/ActionBarReload.js +21 -11
  33. package/dist/primitives/actionBar/ActionBarReload.js.map +1 -1
  34. package/dist/primitives/actionBar/ActionBarSpeak.d.ts +12 -0
  35. package/dist/primitives/actionBar/ActionBarSpeak.d.ts.map +1 -0
  36. package/dist/primitives/actionBar/ActionBarSpeak.js +61 -0
  37. package/dist/primitives/actionBar/ActionBarSpeak.js.map +1 -0
  38. package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts +12 -0
  39. package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts.map +1 -0
  40. package/dist/primitives/actionBar/ActionBarStopSpeaking.js +61 -0
  41. package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -0
  42. package/dist/primitives/actionBar.d.ts +7 -10
  43. package/dist/primitives/actionBar.d.ts.map +1 -1
  44. package/dist/primitives/actionBar.js +6 -2
  45. package/dist/primitives/actionBar.js.map +1 -1
  46. package/dist/primitives/attachment/AttachmentName.d.ts +4 -7
  47. package/dist/primitives/attachment/AttachmentName.d.ts.map +1 -1
  48. package/dist/primitives/attachment/AttachmentRemove.d.ts +4 -7
  49. package/dist/primitives/attachment/AttachmentRemove.d.ts.map +1 -1
  50. package/dist/primitives/attachment/AttachmentRoot.d.ts +5 -8
  51. package/dist/primitives/attachment/AttachmentRoot.d.ts.map +1 -1
  52. package/dist/primitives/attachment/AttachmentThumb.d.ts +4 -7
  53. package/dist/primitives/attachment/AttachmentThumb.d.ts.map +1 -1
  54. package/dist/primitives/attachment.d.ts +4 -9
  55. package/dist/primitives/attachment.d.ts.map +1 -1
  56. package/dist/primitives/branchPicker/BranchPickerCount.d.ts +3 -6
  57. package/dist/primitives/branchPicker/BranchPickerCount.d.ts.map +1 -1
  58. package/dist/primitives/branchPicker/BranchPickerNext.d.ts +7 -7
  59. package/dist/primitives/branchPicker/BranchPickerNext.d.ts.map +1 -1
  60. package/dist/primitives/branchPicker/BranchPickerNext.js +21 -11
  61. package/dist/primitives/branchPicker/BranchPickerNext.js.map +1 -1
  62. package/dist/primitives/branchPicker/BranchPickerNumber.d.ts +3 -6
  63. package/dist/primitives/branchPicker/BranchPickerNumber.d.ts.map +1 -1
  64. package/dist/primitives/branchPicker/BranchPickerPrevious.d.ts +7 -7
  65. package/dist/primitives/branchPicker/BranchPickerPrevious.d.ts.map +1 -1
  66. package/dist/primitives/branchPicker/BranchPickerPrevious.js +21 -11
  67. package/dist/primitives/branchPicker/BranchPickerPrevious.js.map +1 -1
  68. package/dist/primitives/branchPicker.d.ts +4 -9
  69. package/dist/primitives/branchPicker.d.ts.map +1 -1
  70. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.d.ts +4 -7
  71. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.d.ts.map +1 -1
  72. package/dist/primitives/chainOfThought/ChainOfThoughtRoot.d.ts +5 -8
  73. package/dist/primitives/chainOfThought/ChainOfThoughtRoot.d.ts.map +1 -1
  74. package/dist/primitives/chainOfThought.d.ts +3 -8
  75. package/dist/primitives/chainOfThought.d.ts.map +1 -1
  76. package/dist/primitives/composer/ComposerAddAttachment.d.ts +4 -7
  77. package/dist/primitives/composer/ComposerAddAttachment.d.ts.map +1 -1
  78. package/dist/primitives/composer/ComposerAttachments.d.ts +5 -8
  79. package/dist/primitives/composer/ComposerAttachments.d.ts.map +1 -1
  80. package/dist/primitives/composer/ComposerCancel.d.ts +4 -7
  81. package/dist/primitives/composer/ComposerCancel.d.ts.map +1 -1
  82. package/dist/primitives/composer/ComposerIf.d.ts +2 -2
  83. package/dist/primitives/composer/ComposerIf.d.ts.map +1 -0
  84. package/dist/primitives/composer/ComposerInput.d.ts +10 -13
  85. package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
  86. package/dist/primitives/composer/ComposerInput.js +1 -1
  87. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  88. package/dist/primitives/composer/ComposerQueue.d.ts +2 -2
  89. package/dist/primitives/composer/ComposerQueue.d.ts.map +1 -0
  90. package/dist/primitives/composer/ComposerQuote.d.ts +5 -8
  91. package/dist/primitives/composer/ComposerQuote.d.ts.map +1 -1
  92. package/dist/primitives/composer/ComposerQuoteDismiss.d.ts +4 -7
  93. package/dist/primitives/composer/ComposerQuoteDismiss.d.ts.map +1 -1
  94. package/dist/primitives/composer/ComposerQuoteText.d.ts +5 -8
  95. package/dist/primitives/composer/ComposerQuoteText.d.ts.map +1 -1
  96. package/dist/primitives/composer/ComposerRoot.d.ts +5 -8
  97. package/dist/primitives/composer/ComposerRoot.d.ts.map +1 -1
  98. package/dist/primitives/composer/ComposerSend.d.ts +4 -7
  99. package/dist/primitives/composer/ComposerSend.d.ts.map +1 -1
  100. package/dist/primitives/composer.d.ts +11 -16
  101. package/dist/primitives/composer.d.ts.map +1 -1
  102. package/dist/primitives/error/ErrorMessage.d.ts +7 -10
  103. package/dist/primitives/error/ErrorMessage.d.ts.map +1 -1
  104. package/dist/primitives/error/ErrorRoot.d.ts +7 -10
  105. package/dist/primitives/error/ErrorRoot.d.ts.map +1 -1
  106. package/dist/primitives/error/index.d.ts +2 -7
  107. package/dist/primitives/error/index.d.ts.map +1 -1
  108. package/dist/primitives/message/MessageAttachments.d.ts +5 -8
  109. package/dist/primitives/message/MessageAttachments.d.ts.map +1 -1
  110. package/dist/primitives/message/MessageContent.d.ts +47 -49
  111. package/dist/primitives/message/MessageContent.d.ts.map +1 -1
  112. package/dist/primitives/message/MessageContent.js +27 -21
  113. package/dist/primitives/message/MessageContent.js.map +1 -1
  114. package/dist/primitives/message/MessageIf.d.ts +8 -11
  115. package/dist/primitives/message/MessageIf.d.ts.map +1 -1
  116. package/dist/primitives/message/MessageParts.d.ts +6 -8
  117. package/dist/primitives/message/MessageParts.d.ts.map +1 -1
  118. package/dist/primitives/message/MessageRoot.d.ts +5 -8
  119. package/dist/primitives/message/MessageRoot.d.ts.map +1 -1
  120. package/dist/primitives/message.d.ts +6 -11
  121. package/dist/primitives/message.d.ts.map +1 -1
  122. package/dist/primitives/messagePart/MessagePartImage.d.ts +9 -0
  123. package/dist/primitives/messagePart/MessagePartImage.d.ts.map +1 -0
  124. package/dist/primitives/messagePart/MessagePartImage.js +39 -0
  125. package/dist/primitives/messagePart/MessagePartImage.js.map +1 -0
  126. package/dist/primitives/messagePart/MessagePartInProgress.d.ts +2 -0
  127. package/dist/primitives/messagePart/MessagePartInProgress.d.ts.map +1 -0
  128. package/dist/primitives/messagePart/MessagePartInProgress.js +2 -0
  129. package/dist/primitives/messagePart/MessagePartText.d.ts +9 -0
  130. package/dist/primitives/messagePart/MessagePartText.d.ts.map +1 -0
  131. package/dist/primitives/messagePart/MessagePartText.js +32 -0
  132. package/dist/primitives/messagePart/MessagePartText.js.map +1 -0
  133. package/dist/primitives/messagePart.d.ts +5 -0
  134. package/dist/primitives/messagePart.d.ts.map +1 -0
  135. package/dist/primitives/messagePart.js +16 -0
  136. package/dist/primitives/messagePart.js.map +1 -0
  137. package/dist/primitives/queueItem/QueueItemRemove.d.ts +4 -7
  138. package/dist/primitives/queueItem/QueueItemRemove.d.ts.map +1 -1
  139. package/dist/primitives/queueItem/QueueItemSteer.d.ts +4 -7
  140. package/dist/primitives/queueItem/QueueItemSteer.d.ts.map +1 -1
  141. package/dist/primitives/queueItem/QueueItemText.d.ts +5 -8
  142. package/dist/primitives/queueItem/QueueItemText.d.ts.map +1 -1
  143. package/dist/primitives/queueItem.d.ts +3 -8
  144. package/dist/primitives/queueItem.d.ts.map +1 -1
  145. package/dist/primitives/suggestion/SuggestionDescription.d.ts +5 -8
  146. package/dist/primitives/suggestion/SuggestionDescription.d.ts.map +1 -1
  147. package/dist/primitives/suggestion/SuggestionTitle.d.ts +5 -8
  148. package/dist/primitives/suggestion/SuggestionTitle.d.ts.map +1 -1
  149. package/dist/primitives/suggestion/SuggestionTrigger.d.ts +15 -18
  150. package/dist/primitives/suggestion/SuggestionTrigger.d.ts.map +1 -1
  151. package/dist/primitives/suggestion/SuggestionTrigger.js +1 -1
  152. package/dist/primitives/suggestion.d.ts +3 -8
  153. package/dist/primitives/suggestion.d.ts.map +1 -1
  154. package/dist/primitives/thread/ThreadEmpty.d.ts +4 -7
  155. package/dist/primitives/thread/ThreadEmpty.d.ts.map +1 -1
  156. package/dist/primitives/thread/ThreadIf.d.ts +6 -9
  157. package/dist/primitives/thread/ThreadIf.d.ts.map +1 -1
  158. package/dist/primitives/thread/ThreadMessages.d.ts +41 -37
  159. package/dist/primitives/thread/ThreadMessages.d.ts.map +1 -1
  160. package/dist/primitives/thread/ThreadMessages.js +123 -72
  161. package/dist/primitives/thread/ThreadMessages.js.map +1 -1
  162. package/dist/primitives/thread/ThreadRoot.d.ts +5 -8
  163. package/dist/primitives/thread/ThreadRoot.d.ts.map +1 -1
  164. package/dist/primitives/thread/ThreadSuggestion.d.ts +17 -20
  165. package/dist/primitives/thread/ThreadSuggestion.d.ts.map +1 -1
  166. package/dist/primitives/thread.d.ts +8 -11
  167. package/dist/primitives/thread.d.ts.map +1 -1
  168. package/dist/primitives/threadList/ThreadListItems.d.ts +8 -11
  169. package/dist/primitives/threadList/ThreadListItems.d.ts.map +1 -1
  170. package/dist/primitives/threadList/ThreadListItems.js +1 -1
  171. package/dist/primitives/threadList/ThreadListNew.d.ts +7 -10
  172. package/dist/primitives/threadList/ThreadListNew.d.ts.map +1 -1
  173. package/dist/primitives/threadList/ThreadListNew.js +21 -17
  174. package/dist/primitives/threadList/ThreadListNew.js.map +1 -1
  175. package/dist/primitives/threadList/ThreadListRoot.d.ts +5 -8
  176. package/dist/primitives/threadList/ThreadListRoot.d.ts.map +1 -1
  177. package/dist/primitives/threadList.d.ts +3 -8
  178. package/dist/primitives/threadList.d.ts.map +1 -1
  179. package/dist/primitives/threadListItem/ThreadListItemArchive.d.ts +4 -7
  180. package/dist/primitives/threadListItem/ThreadListItemArchive.d.ts.map +1 -1
  181. package/dist/primitives/threadListItem/ThreadListItemDelete.d.ts +4 -7
  182. package/dist/primitives/threadListItem/ThreadListItemDelete.d.ts.map +1 -1
  183. package/dist/primitives/threadListItem/ThreadListItemRoot.d.ts +5 -8
  184. package/dist/primitives/threadListItem/ThreadListItemRoot.d.ts.map +1 -1
  185. package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts +7 -10
  186. package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts.map +1 -1
  187. package/dist/primitives/threadListItem/ThreadListItemTrigger.js +21 -17
  188. package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
  189. package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts +4 -7
  190. package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts.map +1 -1
  191. package/dist/primitives/threadListItem.d.ts +6 -11
  192. package/dist/primitives/threadListItem.d.ts.map +1 -1
  193. package/dist/runtimes/useLocalRuntime.d.ts +2 -2
  194. package/dist/runtimes/useLocalRuntime.d.ts.map +1 -0
  195. package/dist/runtimes/useRemoteThreadListRuntime.d.ts +2 -2
  196. package/dist/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -0
  197. package/package.json +13 -12
  198. package/src/index.ts +91 -1
  199. package/src/primitives/accessibilityRole.test.tsx +20 -0
  200. package/src/primitives/actionBar/ActionBar.test.tsx +375 -0
  201. package/src/primitives/actionBar/ActionBarCopy.tsx +8 -3
  202. package/src/primitives/actionBar/ActionBarEdit.tsx +16 -4
  203. package/src/primitives/actionBar/ActionBarFeedbackNegative.tsx +18 -4
  204. package/src/primitives/actionBar/ActionBarFeedbackPositive.tsx +18 -4
  205. package/src/primitives/actionBar/ActionBarReload.tsx +16 -4
  206. package/src/primitives/actionBar/ActionBarSpeak.tsx +42 -0
  207. package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +42 -0
  208. package/src/primitives/actionBar.ts +8 -0
  209. package/src/primitives/branchPicker/BranchPickerNext.test.tsx +52 -3
  210. package/src/primitives/branchPicker/BranchPickerNext.tsx +16 -4
  211. package/src/primitives/branchPicker/BranchPickerPrevious.test.tsx +52 -6
  212. package/src/primitives/branchPicker/BranchPickerPrevious.tsx +16 -4
  213. package/src/primitives/composer/ComposerInput.test.tsx +17 -1
  214. package/src/primitives/composer/ComposerInput.tsx +6 -1
  215. package/src/primitives/message/MessageContent.test.tsx +64 -3
  216. package/src/primitives/message/MessageContent.tsx +5 -3
  217. package/src/primitives/messagePart/MessagePartImage.test.tsx +91 -0
  218. package/src/primitives/messagePart/MessagePartImage.tsx +33 -0
  219. package/src/primitives/messagePart/MessagePartInProgress.ts +1 -0
  220. package/src/primitives/messagePart/MessagePartText.test.tsx +80 -0
  221. package/src/primitives/messagePart/MessagePartText.tsx +36 -0
  222. package/src/primitives/messagePart.ts +10 -0
  223. package/src/primitives/thread/ThreadMessages.test.tsx +190 -6
  224. package/src/primitives/thread/ThreadMessages.tsx +68 -1
  225. package/src/primitives/threadList/ThreadListNew.test.tsx +35 -8
  226. package/src/primitives/threadList/ThreadListNew.tsx +4 -1
  227. package/src/primitives/threadListItem/ThreadListItemTrigger.test.tsx +32 -6
  228. package/src/primitives/threadListItem/ThreadListItemTrigger.tsx +4 -1
@@ -0,0 +1,42 @@
1
+ import type { ReactNode } from "react";
2
+ import {
3
+ Pressable,
4
+ type PressableProps,
5
+ type PressableStateCallbackType,
6
+ } from "react-native";
7
+ import { useActionBarStopSpeaking } from "@assistant-ui/core/react";
8
+
9
+ export type ActionBarStopSpeakingProps = Omit<
10
+ PressableProps,
11
+ "children" | "onPress"
12
+ > & {
13
+ children:
14
+ | ReactNode
15
+ | ((
16
+ state: PressableStateCallbackType & { disabled: boolean },
17
+ ) => ReactNode);
18
+ };
19
+
20
+ export const ActionBarStopSpeaking = ({
21
+ children,
22
+ disabled: disabledProp,
23
+ ...pressableProps
24
+ }: ActionBarStopSpeakingProps) => {
25
+ const { stopSpeaking, disabled } = useActionBarStopSpeaking();
26
+ const isDisabled = disabledProp ?? disabled;
27
+
28
+ return (
29
+ <Pressable
30
+ onPress={() => stopSpeaking()}
31
+ disabled={isDisabled}
32
+ accessibilityRole="button"
33
+ {...pressableProps}
34
+ >
35
+ {typeof children === "function"
36
+ ? (state) => children({ ...state, disabled: isDisabled })
37
+ : children}
38
+ </Pressable>
39
+ );
40
+ };
41
+
42
+ ActionBarStopSpeaking.displayName = "ActionBarPrimitive.StopSpeaking";
@@ -10,6 +10,14 @@ export {
10
10
  ActionBarReload as Reload,
11
11
  type ActionBarReloadProps as ReloadProps,
12
12
  } from "./actionBar/ActionBarReload";
13
+ export {
14
+ ActionBarSpeak as Speak,
15
+ type ActionBarSpeakProps as SpeakProps,
16
+ } from "./actionBar/ActionBarSpeak";
17
+ export {
18
+ ActionBarStopSpeaking as StopSpeaking,
19
+ type ActionBarStopSpeakingProps as StopSpeakingProps,
20
+ } from "./actionBar/ActionBarStopSpeaking";
13
21
  export {
14
22
  ActionBarFeedbackPositive as FeedbackPositive,
15
23
  type ActionBarFeedbackPositiveProps as FeedbackPositiveProps,
@@ -8,9 +8,17 @@ const h = vi.hoisted(() => ({
8
8
  state: { disabled: false },
9
9
  }));
10
10
 
11
- vi.mock("@assistant-ui/core/react", () => ({
12
- useBranchPickerNext: () => ({ next: h.next, disabled: h.state.disabled }),
13
- }));
11
+ vi.mock("@assistant-ui/core/react", async (importOriginal) => {
12
+ const actual =
13
+ await importOriginal<typeof import("@assistant-ui/core/react")>();
14
+ return {
15
+ ...actual,
16
+ useBranchPickerNext: () => ({
17
+ next: h.next,
18
+ disabled: h.state.disabled,
19
+ }),
20
+ };
21
+ });
14
22
 
15
23
  (globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
16
24
 
@@ -94,4 +102,45 @@ describe("BranchPickerNext", () => {
94
102
 
95
103
  expect(h.next).toHaveBeenCalledTimes(1);
96
104
  });
105
+
106
+ it("passes the hook disabled state and an explicit override to render children", async () => {
107
+ h.state.disabled = true;
108
+ let hookState: unknown = null;
109
+
110
+ await act(async () => {
111
+ root.render(
112
+ <BranchPickerNext testID="t">
113
+ {(state) => {
114
+ hookState = state;
115
+ return state.disabled ? "disabled" : "enabled";
116
+ }}
117
+ </BranchPickerNext>,
118
+ );
119
+ });
120
+
121
+ expect(hookState).toMatchObject({ pressed: false, disabled: true });
122
+ expect(container.textContent).toBe("disabled");
123
+
124
+ let overrideState: unknown = null;
125
+
126
+ await act(async () => {
127
+ root.render(
128
+ <BranchPickerNext testID="t" disabled={false}>
129
+ {(state) => {
130
+ overrideState = state;
131
+ return state.disabled ? "disabled" : "enabled";
132
+ }}
133
+ </BranchPickerNext>,
134
+ );
135
+ });
136
+
137
+ expect(overrideState).toMatchObject({ pressed: false, disabled: false });
138
+ expect(container.textContent).toBe("enabled");
139
+ });
140
+
141
+ it("renders plain children", async () => {
142
+ const el = await mount();
143
+
144
+ expect(el.textContent).toBe("next");
145
+ });
97
146
  });
@@ -1,11 +1,20 @@
1
- import { Pressable, type PressableProps } from "react-native";
1
+ import type { ReactNode } from "react";
2
+ import {
3
+ Pressable,
4
+ type PressableProps,
5
+ type PressableStateCallbackType,
6
+ } from "react-native";
2
7
  import { useBranchPickerNext } from "@assistant-ui/core/react";
3
8
 
4
9
  export type BranchPickerNextProps = Omit<
5
10
  PressableProps,
6
11
  "onPress" | "children"
7
12
  > & {
8
- children: PressableProps["children"];
13
+ children:
14
+ | ReactNode
15
+ | ((
16
+ state: PressableStateCallbackType & { disabled: boolean },
17
+ ) => ReactNode);
9
18
  };
10
19
 
11
20
  export const BranchPickerNext = ({
@@ -14,15 +23,18 @@ export const BranchPickerNext = ({
14
23
  ...pressableProps
15
24
  }: BranchPickerNextProps) => {
16
25
  const { next, disabled } = useBranchPickerNext();
26
+ const isDisabled = disabledProp ?? disabled;
17
27
 
18
28
  return (
19
29
  <Pressable
20
30
  onPress={next}
21
- disabled={disabledProp ?? disabled}
31
+ disabled={isDisabled}
22
32
  accessibilityRole="button"
23
33
  {...pressableProps}
24
34
  >
25
- {children}
35
+ {typeof children === "function"
36
+ ? (state) => children({ ...state, disabled: isDisabled })
37
+ : children}
26
38
  </Pressable>
27
39
  );
28
40
  };
@@ -8,12 +8,17 @@ const h = vi.hoisted(() => ({
8
8
  state: { disabled: false },
9
9
  }));
10
10
 
11
- vi.mock("@assistant-ui/core/react", () => ({
12
- useBranchPickerPrevious: () => ({
13
- previous: h.previous,
14
- disabled: h.state.disabled,
15
- }),
16
- }));
11
+ vi.mock("@assistant-ui/core/react", async (importOriginal) => {
12
+ const actual =
13
+ await importOriginal<typeof import("@assistant-ui/core/react")>();
14
+ return {
15
+ ...actual,
16
+ useBranchPickerPrevious: () => ({
17
+ previous: h.previous,
18
+ disabled: h.state.disabled,
19
+ }),
20
+ };
21
+ });
17
22
 
18
23
  (globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
19
24
 
@@ -97,4 +102,45 @@ describe("BranchPickerPrevious", () => {
97
102
 
98
103
  expect(h.previous).toHaveBeenCalledTimes(1);
99
104
  });
105
+
106
+ it("passes the hook disabled state and an explicit override to render children", async () => {
107
+ h.state.disabled = true;
108
+ let hookState: unknown = null;
109
+
110
+ await act(async () => {
111
+ root.render(
112
+ <BranchPickerPrevious testID="t">
113
+ {(state) => {
114
+ hookState = state;
115
+ return state.disabled ? "disabled" : "enabled";
116
+ }}
117
+ </BranchPickerPrevious>,
118
+ );
119
+ });
120
+
121
+ expect(hookState).toMatchObject({ pressed: false, disabled: true });
122
+ expect(container.textContent).toBe("disabled");
123
+
124
+ let overrideState: unknown = null;
125
+
126
+ await act(async () => {
127
+ root.render(
128
+ <BranchPickerPrevious testID="t" disabled={false}>
129
+ {(state) => {
130
+ overrideState = state;
131
+ return state.disabled ? "disabled" : "enabled";
132
+ }}
133
+ </BranchPickerPrevious>,
134
+ );
135
+ });
136
+
137
+ expect(overrideState).toMatchObject({ pressed: false, disabled: false });
138
+ expect(container.textContent).toBe("enabled");
139
+ });
140
+
141
+ it("renders plain children", async () => {
142
+ const el = await mount();
143
+
144
+ expect(el.textContent).toBe("prev");
145
+ });
100
146
  });
@@ -1,11 +1,20 @@
1
- import { Pressable, type PressableProps } from "react-native";
1
+ import type { ReactNode } from "react";
2
+ import {
3
+ Pressable,
4
+ type PressableProps,
5
+ type PressableStateCallbackType,
6
+ } from "react-native";
2
7
  import { useBranchPickerPrevious } from "@assistant-ui/core/react";
3
8
 
4
9
  export type BranchPickerPreviousProps = Omit<
5
10
  PressableProps,
6
11
  "onPress" | "children"
7
12
  > & {
8
- children: PressableProps["children"];
13
+ children:
14
+ | ReactNode
15
+ | ((
16
+ state: PressableStateCallbackType & { disabled: boolean },
17
+ ) => ReactNode);
9
18
  };
10
19
 
11
20
  export const BranchPickerPrevious = ({
@@ -14,15 +23,18 @@ export const BranchPickerPrevious = ({
14
23
  ...pressableProps
15
24
  }: BranchPickerPreviousProps) => {
16
25
  const { previous, disabled } = useBranchPickerPrevious();
26
+ const isDisabled = disabledProp ?? disabled;
17
27
 
18
28
  return (
19
29
  <Pressable
20
30
  onPress={previous}
21
- disabled={disabledProp ?? disabled}
31
+ disabled={isDisabled}
22
32
  accessibilityRole="button"
23
33
  {...pressableProps}
24
34
  >
25
- {children}
35
+ {typeof children === "function"
36
+ ? (state) => children({ ...state, disabled: isDisabled })
37
+ : children}
26
38
  </Pressable>
27
39
  );
28
40
  };
@@ -8,7 +8,7 @@ const h = vi.hoisted(() => ({
8
8
  sendSpy: vi.fn<() => void>(),
9
9
  flushTapSyncSpy: vi.fn(<T,>(fn: () => T) => fn()),
10
10
  composerState: { text: "" },
11
- threadState: { isRunning: false, queue: false },
11
+ threadState: { isRunning: false, queue: false, voice: false },
12
12
  platform: { os: "web" as "web" | "ios" | "android" },
13
13
  }));
14
14
 
@@ -21,6 +21,7 @@ vi.mock("@assistant-ui/store", () => {
21
21
  getState: () => ({
22
22
  isRunning: h.threadState.isRunning,
23
23
  capabilities: { queue: h.threadState.queue },
24
+ voice: h.threadState.voice ? { status: { type: "running" } } : undefined,
24
25
  }),
25
26
  };
26
27
  const aui = { composer, thread };
@@ -109,6 +110,7 @@ describe("ComposerInput", () => {
109
110
  h.composerState.text = "";
110
111
  h.threadState.isRunning = false;
111
112
  h.threadState.queue = false;
113
+ h.threadState.voice = false;
112
114
  h.platform.os = "web";
113
115
 
114
116
  container = document.createElement("div");
@@ -203,6 +205,20 @@ describe("ComposerInput", () => {
203
205
  expect(h.sendSpy).toHaveBeenCalledTimes(1);
204
206
  });
205
207
 
208
+ it("submits while a spoken reply runs during a voice session", async () => {
209
+ h.threadState.isRunning = true;
210
+ h.threadState.voice = true;
211
+ const input = await mount();
212
+
213
+ let event!: KeyboardEvent;
214
+ await act(async () => {
215
+ event = fireKeyDown(input, { key: "Enter" });
216
+ });
217
+
218
+ expect(h.sendSpy).toHaveBeenCalledTimes(1);
219
+ expect(event.defaultPrevented).toBe(true);
220
+ });
221
+
206
222
  it("blocks submission when the thread starts running after mount", async () => {
207
223
  const input = await mount();
208
224
 
@@ -103,7 +103,12 @@ export const ComposerInput = ({
103
103
  if (nativeEvent.isComposing || nativeEvent.keyCode === 229) return;
104
104
  if (nativeEvent.key === "Enter" && !nativeEvent.shiftKey) {
105
105
  const threadState = aui.thread.getState();
106
- if (threadState.isRunning && !threadState.capabilities.queue) return;
106
+ if (
107
+ threadState.isRunning &&
108
+ !threadState.capabilities.queue &&
109
+ threadState.voice === undefined
110
+ )
111
+ return;
107
112
  (e as unknown as Event).preventDefault?.();
108
113
  aui.composer.send();
109
114
  }
@@ -9,6 +9,7 @@ const h = vi.hoisted(() => ({
9
9
  addToolResult: vi.fn(),
10
10
  resumeToolCall: vi.fn(),
11
11
  respondToToolApproval: vi.fn(),
12
+ unstable_recordInteraction: vi.fn(),
12
13
  state: {
13
14
  message: {
14
15
  content: [] as AnyPart[],
@@ -17,7 +18,10 @@ const h = vi.hoisted(() => ({
17
18
  },
18
19
  },
19
20
  tools: { toolUIs: {} as Record<string, unknown> },
20
- dataRenderers: { renderers: {} as Record<string, unknown> },
21
+ dataRenderers: {
22
+ renderers: {} as Record<string, unknown>,
23
+ fallbacks: [] as unknown[],
24
+ },
21
25
  },
22
26
  }));
23
27
 
@@ -28,6 +32,8 @@ vi.mock("@assistant-ui/store", () => {
28
32
  resumeToolCall: (...args: unknown[]) => h.resumeToolCall(index, ...args),
29
33
  respondToToolApproval: (...args: unknown[]) =>
30
34
  h.respondToToolApproval(index, ...args),
35
+ unstable_recordInteraction: (...args: unknown[]) =>
36
+ h.unstable_recordInteraction(index, ...args),
31
37
  }),
32
38
  });
33
39
  const aui = { message };
@@ -47,9 +53,11 @@ describe("MessageContent", () => {
47
53
  h.addToolResult.mockReset();
48
54
  h.resumeToolCall.mockReset();
49
55
  h.respondToToolApproval.mockReset();
56
+ h.unstable_recordInteraction.mockReset();
50
57
  h.state.message.content = [];
51
58
  h.state.tools.toolUIs = {};
52
59
  h.state.dataRenderers.renderers = {};
60
+ h.state.dataRenderers.fallbacks = [];
53
61
 
54
62
  container = document.createElement("div");
55
63
  document.body.appendChild(container);
@@ -152,6 +160,10 @@ describe("MessageContent", () => {
152
160
  (props.addResult as () => void)();
153
161
  (props.resume as () => void)();
154
162
  (props.respondToApproval as () => void)();
163
+ (props.unstable_recordInteraction as (input: unknown) => void)({
164
+ type: "action",
165
+ payload: { choice: "retry" },
166
+ });
155
167
  return <span data-testid="tool">tool:{String(props.toolName)}</span>;
156
168
  });
157
169
  h.state.tools.toolUIs = { search: [{ render: ToolRender }] };
@@ -163,6 +175,10 @@ describe("MessageContent", () => {
163
175
  expect(h.addToolResult).toHaveBeenCalledWith(0);
164
176
  expect(h.resumeToolCall).toHaveBeenCalledWith(0);
165
177
  expect(h.respondToToolApproval).toHaveBeenCalledWith(0);
178
+ expect(h.unstable_recordInteraction).toHaveBeenCalledWith(0, {
179
+ type: "action",
180
+ payload: { choice: "retry" },
181
+ });
166
182
  });
167
183
 
168
184
  it("picks the first registration when multiple are registered", async () => {
@@ -218,7 +234,7 @@ describe("MessageContent", () => {
218
234
  const DataRender = vi.fn((props: Record<string, unknown>) => (
219
235
  <span data-testid="data">data:{String(props.name)}</span>
220
236
  ));
221
- h.state.dataRenderers.renderers = { chart: DataRender };
237
+ h.state.dataRenderers.renderers = { chart: [DataRender] };
222
238
 
223
239
  await mount();
224
240
 
@@ -251,6 +267,51 @@ describe("MessageContent", () => {
251
267
  expect(el?.textContent).toBe("fallback:chart:0");
252
268
  });
253
269
 
270
+ it("uses dataRenderers.fallbacks[0] before renderData when no named renderer matches", async () => {
271
+ h.state.message.content = [{ type: "data", name: "chart", data: {} }];
272
+ const DataFallback = vi.fn((props: Record<string, unknown>) => (
273
+ <span data-testid="gfallback">global:{String(props.name)}</span>
274
+ ));
275
+ h.state.dataRenderers.fallbacks = [DataFallback];
276
+ const renderData = vi.fn(({ part, index }): ReactElement => (
277
+ <span data-testid="dfallback">
278
+ fallback:{String(part.name)}:{index}
279
+ </span>
280
+ ));
281
+ await mount({ renderData });
282
+
283
+ expect(
284
+ container.querySelector('[data-testid="gfallback"]')?.textContent,
285
+ ).toBe("global:chart");
286
+ expect(DataFallback.mock.calls[0]?.[0]).toEqual({
287
+ type: "data",
288
+ name: "chart",
289
+ data: {},
290
+ });
291
+ expect(renderData).not.toHaveBeenCalled();
292
+ });
293
+
294
+ it("prefers a named data renderer over dataRenderers.fallbacks", async () => {
295
+ h.state.message.content = [{ type: "data", name: "chart", data: {} }];
296
+ const DataRender = vi.fn((props: Record<string, unknown>) => (
297
+ <span data-testid="data">data:{String(props.name)}</span>
298
+ ));
299
+ const DataFallback = vi.fn(() => (
300
+ <span data-testid="gfallback">global-fallback</span>
301
+ ));
302
+ h.state.dataRenderers.renderers = { chart: [DataRender] };
303
+ h.state.dataRenderers.fallbacks = [DataFallback];
304
+ await mount({
305
+ renderData: () => <span data-testid="dfallback">render prop</span>,
306
+ });
307
+
308
+ expect(container.querySelector('[data-testid="data"]')?.textContent).toBe(
309
+ "data:chart",
310
+ );
311
+ expect(DataFallback).not.toHaveBeenCalled();
312
+ expect(container.querySelector('[data-testid="dfallback"]')).toBeNull();
313
+ });
314
+
254
315
  it("renders null when no data renderer is registered and no fallback is given", async () => {
255
316
  h.state.message.content = [{ type: "data", name: "chart", data: {} }];
256
317
  await mount();
@@ -268,7 +329,7 @@ describe("MessageContent", () => {
268
329
  t: [{ render: () => <span>[tool]</span> }],
269
330
  };
270
331
  h.state.dataRenderers.renderers = {
271
- d: () => <span>[data]</span>,
332
+ d: [() => <span>[data]</span>],
272
333
  };
273
334
  await mount();
274
335
 
@@ -81,6 +81,9 @@ const ToolUIDisplay = ({
81
81
  addResult={partMethods.addToolResult}
82
82
  resume={partMethods.resumeToolCall}
83
83
  respondToApproval={partMethods.respondToToolApproval}
84
+ {...(partMethods.unstable_recordInteraction && {
85
+ unstable_recordInteraction: partMethods.unstable_recordInteraction,
86
+ })}
84
87
  />
85
88
  );
86
89
  }
@@ -103,9 +106,8 @@ const DataUIDisplay = ({
103
106
  index: number;
104
107
  }) => {
105
108
  const Render = useAuiState((s) => {
106
- const renders = s.dataRenderers.renderers[part.name];
107
- if (Array.isArray(renders)) return renders[0];
108
- return renders;
109
+ const named = s.dataRenderers.renderers[part.name]?.[0];
110
+ return named ?? s.dataRenderers.fallbacks[0];
109
111
  });
110
112
  if (Render) return <Render {...(part as DataMessagePartProps)} />;
111
113
  if (Fallback) return <Fallback part={part} index={index} />;
@@ -0,0 +1,91 @@
1
+ import { act } from "react";
2
+ import { createRoot, type Root } from "react-dom/client";
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+ import type { ThreadMessageLike } from "@assistant-ui/core";
5
+ import {
6
+ AssistantRuntimeProvider,
7
+ MessageByIndexProvider,
8
+ PartByIndexProvider,
9
+ useExternalStoreRuntime,
10
+ } from "@assistant-ui/core/react";
11
+ import { MessagePartPrimitiveImage } from "./MessagePartImage";
12
+
13
+ vi.mock("react-native", async (importOriginal) => {
14
+ const actual = await importOriginal<typeof import("react-native")>();
15
+ const React = await import("react");
16
+
17
+ const ImageMock = React.forwardRef(function ImageMock(
18
+ props: { source?: { uri?: string }; testID?: string },
19
+ ref: React.Ref<HTMLImageElement>,
20
+ ) {
21
+ return React.createElement("img", {
22
+ ref,
23
+ src: props.source?.uri,
24
+ "data-testid": props.testID,
25
+ });
26
+ });
27
+
28
+ return { ...actual, Image: ImageMock };
29
+ });
30
+
31
+ (globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
32
+
33
+ const messages: ThreadMessageLike[] = [
34
+ {
35
+ role: "assistant",
36
+ content: [
37
+ { type: "text", text: "Answer" },
38
+ { type: "image", image: "https://example.com/image.png" },
39
+ ],
40
+ },
41
+ ];
42
+
43
+ const App = ({ partIndex }: { partIndex: number }) => {
44
+ const runtime = useExternalStoreRuntime({
45
+ messages,
46
+ convertMessage: (message) => message,
47
+ onNew: async () => {
48
+ throw new Error("This thread is read-only");
49
+ },
50
+ });
51
+
52
+ return (
53
+ <AssistantRuntimeProvider runtime={runtime}>
54
+ <MessageByIndexProvider index={0}>
55
+ <PartByIndexProvider index={partIndex}>
56
+ <MessagePartPrimitiveImage testID="image" />
57
+ </PartByIndexProvider>
58
+ </MessageByIndexProvider>
59
+ </AssistantRuntimeProvider>
60
+ );
61
+ };
62
+
63
+ describe("MessagePartPrimitiveImage", () => {
64
+ let container: HTMLDivElement;
65
+ let root: Root;
66
+
67
+ beforeEach(() => {
68
+ container = document.createElement("div");
69
+ document.body.appendChild(container);
70
+ root = createRoot(container);
71
+ });
72
+
73
+ afterEach(async () => {
74
+ await act(async () => root.unmount());
75
+ container.remove();
76
+ });
77
+
78
+ const mount = async (partIndex: number) => {
79
+ await act(async () => root.render(<App partIndex={partIndex} />));
80
+ return container.querySelector<HTMLImageElement>('[data-testid="image"]');
81
+ };
82
+
83
+ it("renders the current image part URI", async () => {
84
+ const image = await mount(1);
85
+ expect(image?.getAttribute("src")).toBe("https://example.com/image.png");
86
+ });
87
+
88
+ it("renders nothing for a non-image part", async () => {
89
+ expect(await mount(0)).toBeNull();
90
+ });
91
+ });
@@ -0,0 +1,33 @@
1
+ import {
2
+ type ComponentRef,
3
+ type ForwardRefExoticComponent,
4
+ forwardRef,
5
+ type RefAttributes,
6
+ } from "react";
7
+ import { Image, type ImageProps } from "react-native";
8
+ import { useAuiState } from "@assistant-ui/store";
9
+
10
+ export namespace MessagePartPrimitiveImage {
11
+ export type Element = ComponentRef<typeof Image>;
12
+ export type Props = Omit<ImageProps, "source">;
13
+ }
14
+
15
+ export const MessagePartPrimitiveImage: ForwardRefExoticComponent<
16
+ MessagePartPrimitiveImage.Props &
17
+ RefAttributes<MessagePartPrimitiveImage.Element>
18
+ > = forwardRef<
19
+ MessagePartPrimitiveImage.Element,
20
+ MessagePartPrimitiveImage.Props
21
+ >((props, forwardedRef) => {
22
+ const uri = useAuiState((s) => {
23
+ const part = s.part;
24
+ return part.type === "image" ? part.image : undefined;
25
+ });
26
+
27
+ if (uri === undefined) return null;
28
+ return <Image source={{ uri }} {...props} ref={forwardedRef} />;
29
+ });
30
+
31
+ MessagePartPrimitiveImage.displayName = "MessagePartPrimitive.Image";
32
+
33
+ export type MessagePartImageProps = MessagePartPrimitiveImage.Props;
@@ -0,0 +1 @@
1
+ export { MessagePartPrimitiveInProgress } from "@assistant-ui/core/react";