@assistant-ui/react-native 0.1.42 → 0.1.43

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 (167) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +2 -1
  3. package/dist/primitives/actionBar/ActionBarCopy.d.ts +3 -3
  4. package/dist/primitives/actionBar/ActionBarCopy.d.ts.map +1 -1
  5. package/dist/primitives/actionBar/ActionBarCopy.js +23 -19
  6. package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
  7. package/dist/primitives/actionBar/ActionBarEdit.d.ts +7 -5
  8. package/dist/primitives/actionBar/ActionBarEdit.d.ts.map +1 -1
  9. package/dist/primitives/actionBar/ActionBarEdit.js +21 -11
  10. package/dist/primitives/actionBar/ActionBarEdit.js.map +1 -1
  11. package/dist/primitives/actionBar/ActionBarFeedbackNegative.d.ts +3 -3
  12. package/dist/primitives/actionBar/ActionBarFeedbackNegative.d.ts.map +1 -1
  13. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +26 -16
  14. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
  15. package/dist/primitives/actionBar/ActionBarFeedbackPositive.d.ts +3 -3
  16. package/dist/primitives/actionBar/ActionBarFeedbackPositive.d.ts.map +1 -1
  17. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +26 -16
  18. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
  19. package/dist/primitives/actionBar/ActionBarReload.d.ts +7 -5
  20. package/dist/primitives/actionBar/ActionBarReload.d.ts.map +1 -1
  21. package/dist/primitives/actionBar/ActionBarReload.js +21 -11
  22. package/dist/primitives/actionBar/ActionBarReload.js.map +1 -1
  23. package/dist/primitives/actionBar/ActionBarSpeak.d.ts +14 -0
  24. package/dist/primitives/actionBar/ActionBarSpeak.d.ts.map +1 -0
  25. package/dist/primitives/actionBar/ActionBarSpeak.js +61 -0
  26. package/dist/primitives/actionBar/ActionBarSpeak.js.map +1 -0
  27. package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts +14 -0
  28. package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts.map +1 -0
  29. package/dist/primitives/actionBar/ActionBarStopSpeaking.js +61 -0
  30. package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -0
  31. package/dist/primitives/actionBar.d.ts +4 -2
  32. package/dist/primitives/actionBar.js +6 -2
  33. package/dist/primitives/actionBar.js.map +1 -1
  34. package/dist/primitives/attachment/AttachmentName.d.ts +2 -3
  35. package/dist/primitives/attachment/AttachmentName.d.ts.map +1 -1
  36. package/dist/primitives/attachment/AttachmentRemove.d.ts +2 -3
  37. package/dist/primitives/attachment/AttachmentRemove.d.ts.map +1 -1
  38. package/dist/primitives/attachment/AttachmentRoot.d.ts +2 -3
  39. package/dist/primitives/attachment/AttachmentRoot.d.ts.map +1 -1
  40. package/dist/primitives/attachment/AttachmentThumb.d.ts +2 -3
  41. package/dist/primitives/attachment/AttachmentThumb.d.ts.map +1 -1
  42. package/dist/primitives/branchPicker/BranchPickerCount.d.ts +2 -3
  43. package/dist/primitives/branchPicker/BranchPickerCount.d.ts.map +1 -1
  44. package/dist/primitives/branchPicker/BranchPickerNext.d.ts +7 -5
  45. package/dist/primitives/branchPicker/BranchPickerNext.d.ts.map +1 -1
  46. package/dist/primitives/branchPicker/BranchPickerNext.js +21 -11
  47. package/dist/primitives/branchPicker/BranchPickerNext.js.map +1 -1
  48. package/dist/primitives/branchPicker/BranchPickerNumber.d.ts +2 -3
  49. package/dist/primitives/branchPicker/BranchPickerNumber.d.ts.map +1 -1
  50. package/dist/primitives/branchPicker/BranchPickerPrevious.d.ts +7 -5
  51. package/dist/primitives/branchPicker/BranchPickerPrevious.d.ts.map +1 -1
  52. package/dist/primitives/branchPicker/BranchPickerPrevious.js +21 -11
  53. package/dist/primitives/branchPicker/BranchPickerPrevious.js.map +1 -1
  54. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.d.ts +2 -3
  55. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.d.ts.map +1 -1
  56. package/dist/primitives/chainOfThought/ChainOfThoughtRoot.d.ts +2 -3
  57. package/dist/primitives/chainOfThought/ChainOfThoughtRoot.d.ts.map +1 -1
  58. package/dist/primitives/composer/ComposerAddAttachment.d.ts +2 -3
  59. package/dist/primitives/composer/ComposerAddAttachment.d.ts.map +1 -1
  60. package/dist/primitives/composer/ComposerAttachments.d.ts +4 -5
  61. package/dist/primitives/composer/ComposerAttachments.d.ts.map +1 -1
  62. package/dist/primitives/composer/ComposerCancel.d.ts +2 -3
  63. package/dist/primitives/composer/ComposerCancel.d.ts.map +1 -1
  64. package/dist/primitives/composer/ComposerInput.d.ts +2 -3
  65. package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
  66. package/dist/primitives/composer/ComposerQuote.d.ts +2 -3
  67. package/dist/primitives/composer/ComposerQuote.d.ts.map +1 -1
  68. package/dist/primitives/composer/ComposerQuoteDismiss.d.ts +2 -3
  69. package/dist/primitives/composer/ComposerQuoteDismiss.d.ts.map +1 -1
  70. package/dist/primitives/composer/ComposerQuoteText.d.ts +2 -3
  71. package/dist/primitives/composer/ComposerQuoteText.d.ts.map +1 -1
  72. package/dist/primitives/composer/ComposerRoot.d.ts +2 -3
  73. package/dist/primitives/composer/ComposerRoot.d.ts.map +1 -1
  74. package/dist/primitives/composer/ComposerSend.d.ts +2 -3
  75. package/dist/primitives/composer/ComposerSend.d.ts.map +1 -1
  76. package/dist/primitives/error/ErrorMessage.d.ts +2 -3
  77. package/dist/primitives/error/ErrorMessage.d.ts.map +1 -1
  78. package/dist/primitives/error/ErrorRoot.d.ts +2 -3
  79. package/dist/primitives/error/ErrorRoot.d.ts.map +1 -1
  80. package/dist/primitives/message/MessageAttachments.d.ts +4 -5
  81. package/dist/primitives/message/MessageAttachments.d.ts.map +1 -1
  82. package/dist/primitives/message/MessageContent.d.ts +2 -3
  83. package/dist/primitives/message/MessageContent.d.ts.map +1 -1
  84. package/dist/primitives/message/MessageIf.d.ts +2 -3
  85. package/dist/primitives/message/MessageIf.d.ts.map +1 -1
  86. package/dist/primitives/message/MessageParts.d.ts +3 -3
  87. package/dist/primitives/message/MessageParts.d.ts.map +1 -1
  88. package/dist/primitives/message/MessageRoot.d.ts +2 -3
  89. package/dist/primitives/message/MessageRoot.d.ts.map +1 -1
  90. package/dist/primitives/messagePart/MessagePartImage.d.ts +11 -0
  91. package/dist/primitives/messagePart/MessagePartImage.d.ts.map +1 -0
  92. package/dist/primitives/messagePart/MessagePartImage.js +39 -0
  93. package/dist/primitives/messagePart/MessagePartImage.js.map +1 -0
  94. package/dist/primitives/messagePart/MessagePartInProgress.d.ts +2 -0
  95. package/dist/primitives/messagePart/MessagePartInProgress.js +2 -0
  96. package/dist/primitives/messagePart/MessagePartText.d.ts +11 -0
  97. package/dist/primitives/messagePart/MessagePartText.d.ts.map +1 -0
  98. package/dist/primitives/messagePart/MessagePartText.js +32 -0
  99. package/dist/primitives/messagePart/MessagePartText.js.map +1 -0
  100. package/dist/primitives/messagePart.d.ts +10 -0
  101. package/dist/primitives/messagePart.d.ts.map +1 -0
  102. package/dist/primitives/messagePart.js +16 -0
  103. package/dist/primitives/messagePart.js.map +1 -0
  104. package/dist/primitives/queueItem/QueueItemRemove.d.ts +2 -3
  105. package/dist/primitives/queueItem/QueueItemRemove.d.ts.map +1 -1
  106. package/dist/primitives/queueItem/QueueItemSteer.d.ts +2 -3
  107. package/dist/primitives/queueItem/QueueItemSteer.d.ts.map +1 -1
  108. package/dist/primitives/queueItem/QueueItemText.d.ts +2 -3
  109. package/dist/primitives/queueItem/QueueItemText.d.ts.map +1 -1
  110. package/dist/primitives/suggestion/SuggestionDescription.d.ts +2 -3
  111. package/dist/primitives/suggestion/SuggestionDescription.d.ts.map +1 -1
  112. package/dist/primitives/suggestion/SuggestionTitle.d.ts +2 -3
  113. package/dist/primitives/suggestion/SuggestionTitle.d.ts.map +1 -1
  114. package/dist/primitives/suggestion/SuggestionTrigger.d.ts +2 -3
  115. package/dist/primitives/suggestion/SuggestionTrigger.d.ts.map +1 -1
  116. package/dist/primitives/thread/ThreadEmpty.d.ts +2 -3
  117. package/dist/primitives/thread/ThreadEmpty.d.ts.map +1 -1
  118. package/dist/primitives/thread/ThreadIf.d.ts +2 -3
  119. package/dist/primitives/thread/ThreadIf.d.ts.map +1 -1
  120. package/dist/primitives/thread/ThreadMessages.d.ts +9 -5
  121. package/dist/primitives/thread/ThreadMessages.d.ts.map +1 -1
  122. package/dist/primitives/thread/ThreadMessages.js +117 -78
  123. package/dist/primitives/thread/ThreadMessages.js.map +1 -1
  124. package/dist/primitives/thread/ThreadRoot.d.ts +2 -3
  125. package/dist/primitives/thread/ThreadRoot.d.ts.map +1 -1
  126. package/dist/primitives/thread/ThreadSuggestion.d.ts +2 -3
  127. package/dist/primitives/thread/ThreadSuggestion.d.ts.map +1 -1
  128. package/dist/primitives/threadList/ThreadListItems.d.ts +2 -3
  129. package/dist/primitives/threadList/ThreadListItems.d.ts.map +1 -1
  130. package/dist/primitives/threadList/ThreadListNew.d.ts +2 -3
  131. package/dist/primitives/threadList/ThreadListNew.d.ts.map +1 -1
  132. package/dist/primitives/threadList/ThreadListRoot.d.ts +2 -3
  133. package/dist/primitives/threadList/ThreadListRoot.d.ts.map +1 -1
  134. package/dist/primitives/threadListItem/ThreadListItemArchive.d.ts +2 -3
  135. package/dist/primitives/threadListItem/ThreadListItemArchive.d.ts.map +1 -1
  136. package/dist/primitives/threadListItem/ThreadListItemDelete.d.ts +2 -3
  137. package/dist/primitives/threadListItem/ThreadListItemDelete.d.ts.map +1 -1
  138. package/dist/primitives/threadListItem/ThreadListItemRoot.d.ts +2 -3
  139. package/dist/primitives/threadListItem/ThreadListItemRoot.d.ts.map +1 -1
  140. package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts +2 -3
  141. package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts.map +1 -1
  142. package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts +2 -3
  143. package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts.map +1 -1
  144. package/package.json +13 -12
  145. package/src/index.ts +1 -0
  146. package/src/primitives/accessibilityRole.test.tsx +20 -0
  147. package/src/primitives/actionBar/ActionBar.test.tsx +299 -0
  148. package/src/primitives/actionBar/ActionBarCopy.tsx +8 -3
  149. package/src/primitives/actionBar/ActionBarEdit.tsx +16 -4
  150. package/src/primitives/actionBar/ActionBarFeedbackNegative.tsx +14 -3
  151. package/src/primitives/actionBar/ActionBarFeedbackPositive.tsx +14 -3
  152. package/src/primitives/actionBar/ActionBarReload.tsx +16 -4
  153. package/src/primitives/actionBar/ActionBarSpeak.tsx +42 -0
  154. package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +42 -0
  155. package/src/primitives/actionBar.ts +8 -0
  156. package/src/primitives/branchPicker/BranchPickerNext.test.tsx +52 -3
  157. package/src/primitives/branchPicker/BranchPickerNext.tsx +16 -4
  158. package/src/primitives/branchPicker/BranchPickerPrevious.test.tsx +52 -6
  159. package/src/primitives/branchPicker/BranchPickerPrevious.tsx +16 -4
  160. package/src/primitives/messagePart/MessagePartImage.test.tsx +91 -0
  161. package/src/primitives/messagePart/MessagePartImage.tsx +33 -0
  162. package/src/primitives/messagePart/MessagePartInProgress.ts +1 -0
  163. package/src/primitives/messagePart/MessagePartText.test.tsx +80 -0
  164. package/src/primitives/messagePart/MessagePartText.tsx +36 -0
  165. package/src/primitives/messagePart.ts +10 -0
  166. package/src/primitives/thread/ThreadMessages.test.tsx +79 -1
  167. package/src/primitives/thread/ThreadMessages.tsx +37 -0
@@ -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 { useActionBarSpeak } from "@assistant-ui/core/react";
8
+
9
+ export type ActionBarSpeakProps = Omit<
10
+ PressableProps,
11
+ "children" | "onPress"
12
+ > & {
13
+ children:
14
+ | ReactNode
15
+ | ((
16
+ state: PressableStateCallbackType & { disabled: boolean },
17
+ ) => ReactNode);
18
+ };
19
+
20
+ export const ActionBarSpeak = ({
21
+ children,
22
+ disabled: disabledProp,
23
+ ...pressableProps
24
+ }: ActionBarSpeakProps) => {
25
+ const { speak, disabled } = useActionBarSpeak();
26
+ const isDisabled = disabledProp ?? disabled;
27
+
28
+ return (
29
+ <Pressable
30
+ onPress={() => void speak()}
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
+ ActionBarSpeak.displayName = "ActionBarPrimitive.Speak";
@@ -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
  };
@@ -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";
@@ -0,0 +1,80 @@
1
+ import { act } from "react";
2
+ import { createRoot, type Root } from "react-dom/client";
3
+ import { afterEach, beforeEach, describe, expect, it } 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 { MessagePartPrimitiveText } from "./MessagePartText";
12
+
13
+ (globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
14
+
15
+ const messages: ThreadMessageLike[] = [
16
+ {
17
+ role: "assistant",
18
+ content: [
19
+ { type: "text", text: "Answer" },
20
+ { type: "reasoning", text: "Thinking" },
21
+ { type: "image", image: "https://example.com/image.png" },
22
+ ],
23
+ },
24
+ ];
25
+
26
+ const App = ({ partIndex }: { partIndex: number }) => {
27
+ const runtime = useExternalStoreRuntime({
28
+ messages,
29
+ convertMessage: (message) => message,
30
+ onNew: async () => {
31
+ throw new Error("This thread is read-only");
32
+ },
33
+ });
34
+
35
+ return (
36
+ <AssistantRuntimeProvider runtime={runtime}>
37
+ <MessageByIndexProvider index={0}>
38
+ <PartByIndexProvider index={partIndex}>
39
+ <MessagePartPrimitiveText testID="text" />
40
+ </PartByIndexProvider>
41
+ </MessageByIndexProvider>
42
+ </AssistantRuntimeProvider>
43
+ );
44
+ };
45
+
46
+ describe("MessagePartPrimitiveText", () => {
47
+ let container: HTMLDivElement;
48
+ let root: Root;
49
+
50
+ beforeEach(() => {
51
+ container = document.createElement("div");
52
+ document.body.appendChild(container);
53
+ root = createRoot(container);
54
+ });
55
+
56
+ afterEach(async () => {
57
+ await act(async () => root.unmount());
58
+ container.remove();
59
+ });
60
+
61
+ const mount = async (partIndex: number) => {
62
+ await act(async () => root.render(<App partIndex={partIndex} />));
63
+ return container.querySelector('[data-testid="text"]') as HTMLElement;
64
+ };
65
+
66
+ it("renders text from the current text part", async () => {
67
+ const text = await mount(0);
68
+ expect(text.textContent).toBe("Answer");
69
+ });
70
+
71
+ it("renders text from the current reasoning part", async () => {
72
+ const text = await mount(1);
73
+ expect(text.textContent).toBe("Thinking");
74
+ });
75
+
76
+ it("renders empty text for a non-text part", async () => {
77
+ const text = await mount(2);
78
+ expect(text.textContent).toBe("");
79
+ });
80
+ });
@@ -0,0 +1,36 @@
1
+ import {
2
+ type ComponentRef,
3
+ type ForwardRefExoticComponent,
4
+ forwardRef,
5
+ type RefAttributes,
6
+ } from "react";
7
+ import { Text, type TextProps } from "react-native";
8
+ import { useAuiState } from "@assistant-ui/store";
9
+
10
+ export namespace MessagePartPrimitiveText {
11
+ export type Element = ComponentRef<typeof Text>;
12
+ export type Props = Omit<TextProps, "children">;
13
+ }
14
+
15
+ export const MessagePartPrimitiveText: ForwardRefExoticComponent<
16
+ MessagePartPrimitiveText.Props &
17
+ RefAttributes<MessagePartPrimitiveText.Element>
18
+ > = forwardRef<
19
+ MessagePartPrimitiveText.Element,
20
+ MessagePartPrimitiveText.Props
21
+ >((props, forwardedRef) => {
22
+ const text = useAuiState((s) => {
23
+ const part = s.part;
24
+ return part.type === "text" || part.type === "reasoning" ? part.text : "";
25
+ });
26
+
27
+ return (
28
+ <Text {...props} ref={forwardedRef}>
29
+ {text}
30
+ </Text>
31
+ );
32
+ });
33
+
34
+ MessagePartPrimitiveText.displayName = "MessagePartPrimitive.Text";
35
+
36
+ export type MessagePartTextProps = MessagePartPrimitiveText.Props;
@@ -0,0 +1,10 @@
1
+ export {
2
+ MessagePartPrimitiveText as Text,
3
+ type MessagePartTextProps as TextProps,
4
+ } from "./messagePart/MessagePartText";
5
+ export {
6
+ MessagePartPrimitiveImage as Image,
7
+ type MessagePartImageProps as ImageProps,
8
+ } from "./messagePart/MessagePartImage";
9
+ export { MessagePartPrimitiveInProgress as InProgress } from "./messagePart/MessagePartInProgress";
10
+ export { PartPrimitiveMessages as Messages } from "@assistant-ui/core/react";
@@ -1,7 +1,7 @@
1
1
  import { act, createRef } from "react";
2
2
  import { createRoot, type Root } from "react-dom/client";
3
3
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
- import type { FlatList } from "react-native";
4
+ import type { FlatList, FlatListProps } from "react-native";
5
5
  import type { ThreadMessage } from "@assistant-ui/core";
6
6
  import { ThreadMessages, ThreadMessagesFlatList } from "./ThreadMessages";
7
7
 
@@ -154,6 +154,10 @@ describe("ThreadMessages", () => {
154
154
  onContentSizeChange?: (width: number, height: number) => void;
155
155
  onLayout?: (event: unknown) => void;
156
156
  onScroll?: (event: unknown) => void;
157
+ onStartReached?: NonNullable<
158
+ FlatListProps<ThreadMessage>["onStartReached"]
159
+ >;
160
+ onStartReachedThreshold?: number;
157
161
  scrollEventThrottle?: number;
158
162
  } | null;
159
163
  if (!props) throw new Error("FlatList was not rendered");
@@ -392,6 +396,80 @@ describe("ThreadMessages", () => {
392
396
  expect(h.scrollToOffset).not.toHaveBeenCalled();
393
397
  });
394
398
 
399
+ describe("MessagesFlatList history", () => {
400
+ it("wires loadMore only while history can load more", async () => {
401
+ const loadMore = vi.fn();
402
+
403
+ await mountFlatList({
404
+ components: messageComponents,
405
+ history: { hasMore: true, isLoadingMore: false, loadMore },
406
+ });
407
+ getFlatListProps().onStartReached?.({ distanceFromStart: 0 });
408
+ expect(loadMore).toHaveBeenCalledOnce();
409
+
410
+ await mountFlatList({
411
+ components: messageComponents,
412
+ history: { hasMore: false, isLoadingMore: false, loadMore },
413
+ });
414
+ expect(getFlatListProps().onStartReached).toBeUndefined();
415
+
416
+ await mountFlatList({
417
+ components: messageComponents,
418
+ history: { hasMore: true, isLoadingMore: true, loadMore },
419
+ });
420
+ expect(getFlatListProps().onStartReached).toBeUndefined();
421
+ });
422
+
423
+ it("defaults the history threshold and preserves a caller override", async () => {
424
+ const history = {
425
+ hasMore: true,
426
+ isLoadingMore: false,
427
+ loadMore: vi.fn(),
428
+ };
429
+
430
+ await mountFlatList({ components: messageComponents, history });
431
+ expect(getFlatListProps().onStartReachedThreshold).toBe(1);
432
+
433
+ await mountFlatList({
434
+ components: messageComponents,
435
+ history,
436
+ onStartReachedThreshold: 0.5,
437
+ });
438
+ expect(getFlatListProps().onStartReachedThreshold).toBe(0.5);
439
+ });
440
+
441
+ it("runs a caller onStartReached before loading more history", async () => {
442
+ const calls: string[] = [];
443
+ const info = { distanceFromStart: 42 };
444
+ const onStartReached = vi.fn(() => calls.push("onStartReached"));
445
+
446
+ await mountFlatList({
447
+ components: messageComponents,
448
+ history: {
449
+ hasMore: true,
450
+ isLoadingMore: false,
451
+ loadMore: () => calls.push("loadMore"),
452
+ },
453
+ onStartReached,
454
+ });
455
+
456
+ getFlatListProps().onStartReached?.(info);
457
+
458
+ expect(onStartReached).toHaveBeenCalledWith(info);
459
+ expect(calls).toEqual(["onStartReached", "loadMore"]);
460
+ });
461
+
462
+ it("leaves start-reached props untouched without history", async () => {
463
+ const onStartReached = vi.fn();
464
+
465
+ await mountFlatList({ components: messageComponents, onStartReached });
466
+
467
+ const props = getFlatListProps();
468
+ expect(props.onStartReached).toBe(onStartReached);
469
+ expect(props).not.toHaveProperty("onStartReachedThreshold");
470
+ });
471
+ });
472
+
395
473
  describe("MessagesFlatList auto-scroll", () => {
396
474
  it("scrolls to the bottom when messages first appear", async () => {
397
475
  h.state.thread.messages = [{ id: "1", role: "user" }];