@assistant-ui/react-native 0.1.41 → 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 (170) hide show
  1. package/dist/index.d.ts +5 -4
  2. package/dist/index.js +4 -3
  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 +4 -7
  59. package/dist/primitives/composer/ComposerAddAttachment.d.ts.map +1 -1
  60. package/dist/primitives/composer/ComposerAddAttachment.js +2 -4
  61. package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
  62. package/dist/primitives/composer/ComposerAttachments.d.ts +4 -5
  63. package/dist/primitives/composer/ComposerAttachments.d.ts.map +1 -1
  64. package/dist/primitives/composer/ComposerCancel.d.ts +2 -3
  65. package/dist/primitives/composer/ComposerCancel.d.ts.map +1 -1
  66. package/dist/primitives/composer/ComposerInput.d.ts +2 -3
  67. package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
  68. package/dist/primitives/composer/ComposerQuote.d.ts +2 -3
  69. package/dist/primitives/composer/ComposerQuote.d.ts.map +1 -1
  70. package/dist/primitives/composer/ComposerQuoteDismiss.d.ts +2 -3
  71. package/dist/primitives/composer/ComposerQuoteDismiss.d.ts.map +1 -1
  72. package/dist/primitives/composer/ComposerQuoteText.d.ts +2 -3
  73. package/dist/primitives/composer/ComposerQuoteText.d.ts.map +1 -1
  74. package/dist/primitives/composer/ComposerRoot.d.ts +2 -3
  75. package/dist/primitives/composer/ComposerRoot.d.ts.map +1 -1
  76. package/dist/primitives/composer/ComposerSend.d.ts +2 -3
  77. package/dist/primitives/composer/ComposerSend.d.ts.map +1 -1
  78. package/dist/primitives/error/ErrorMessage.d.ts +2 -3
  79. package/dist/primitives/error/ErrorMessage.d.ts.map +1 -1
  80. package/dist/primitives/error/ErrorRoot.d.ts +2 -3
  81. package/dist/primitives/error/ErrorRoot.d.ts.map +1 -1
  82. package/dist/primitives/message/MessageAttachments.d.ts +4 -5
  83. package/dist/primitives/message/MessageAttachments.d.ts.map +1 -1
  84. package/dist/primitives/message/MessageContent.d.ts +2 -3
  85. package/dist/primitives/message/MessageContent.d.ts.map +1 -1
  86. package/dist/primitives/message/MessageIf.d.ts +2 -3
  87. package/dist/primitives/message/MessageIf.d.ts.map +1 -1
  88. package/dist/primitives/message/MessageParts.d.ts +3 -3
  89. package/dist/primitives/message/MessageParts.d.ts.map +1 -1
  90. package/dist/primitives/message/MessageRoot.d.ts +2 -3
  91. package/dist/primitives/message/MessageRoot.d.ts.map +1 -1
  92. package/dist/primitives/messagePart/MessagePartImage.d.ts +11 -0
  93. package/dist/primitives/messagePart/MessagePartImage.d.ts.map +1 -0
  94. package/dist/primitives/messagePart/MessagePartImage.js +39 -0
  95. package/dist/primitives/messagePart/MessagePartImage.js.map +1 -0
  96. package/dist/primitives/messagePart/MessagePartInProgress.d.ts +2 -0
  97. package/dist/primitives/messagePart/MessagePartInProgress.js +2 -0
  98. package/dist/primitives/messagePart/MessagePartText.d.ts +11 -0
  99. package/dist/primitives/messagePart/MessagePartText.d.ts.map +1 -0
  100. package/dist/primitives/messagePart/MessagePartText.js +32 -0
  101. package/dist/primitives/messagePart/MessagePartText.js.map +1 -0
  102. package/dist/primitives/messagePart.d.ts +10 -0
  103. package/dist/primitives/messagePart.d.ts.map +1 -0
  104. package/dist/primitives/messagePart.js +16 -0
  105. package/dist/primitives/messagePart.js.map +1 -0
  106. package/dist/primitives/queueItem/QueueItemRemove.d.ts +2 -3
  107. package/dist/primitives/queueItem/QueueItemRemove.d.ts.map +1 -1
  108. package/dist/primitives/queueItem/QueueItemSteer.d.ts +2 -3
  109. package/dist/primitives/queueItem/QueueItemSteer.d.ts.map +1 -1
  110. package/dist/primitives/queueItem/QueueItemText.d.ts +2 -3
  111. package/dist/primitives/queueItem/QueueItemText.d.ts.map +1 -1
  112. package/dist/primitives/suggestion/SuggestionDescription.d.ts +2 -3
  113. package/dist/primitives/suggestion/SuggestionDescription.d.ts.map +1 -1
  114. package/dist/primitives/suggestion/SuggestionTitle.d.ts +2 -3
  115. package/dist/primitives/suggestion/SuggestionTitle.d.ts.map +1 -1
  116. package/dist/primitives/suggestion/SuggestionTrigger.d.ts +2 -3
  117. package/dist/primitives/suggestion/SuggestionTrigger.d.ts.map +1 -1
  118. package/dist/primitives/thread/ThreadEmpty.d.ts +2 -3
  119. package/dist/primitives/thread/ThreadEmpty.d.ts.map +1 -1
  120. package/dist/primitives/thread/ThreadIf.d.ts +2 -3
  121. package/dist/primitives/thread/ThreadIf.d.ts.map +1 -1
  122. package/dist/primitives/thread/ThreadMessages.d.ts +9 -5
  123. package/dist/primitives/thread/ThreadMessages.d.ts.map +1 -1
  124. package/dist/primitives/thread/ThreadMessages.js +244 -171
  125. package/dist/primitives/thread/ThreadMessages.js.map +1 -1
  126. package/dist/primitives/thread/ThreadRoot.d.ts +2 -3
  127. package/dist/primitives/thread/ThreadRoot.d.ts.map +1 -1
  128. package/dist/primitives/thread/ThreadSuggestion.d.ts +2 -3
  129. package/dist/primitives/thread/ThreadSuggestion.d.ts.map +1 -1
  130. package/dist/primitives/threadList/ThreadListItems.d.ts +2 -3
  131. package/dist/primitives/threadList/ThreadListItems.d.ts.map +1 -1
  132. package/dist/primitives/threadList/ThreadListNew.d.ts +2 -3
  133. package/dist/primitives/threadList/ThreadListNew.d.ts.map +1 -1
  134. package/dist/primitives/threadList/ThreadListRoot.d.ts +2 -3
  135. package/dist/primitives/threadList/ThreadListRoot.d.ts.map +1 -1
  136. package/dist/primitives/threadListItem/ThreadListItemArchive.d.ts +2 -3
  137. package/dist/primitives/threadListItem/ThreadListItemArchive.d.ts.map +1 -1
  138. package/dist/primitives/threadListItem/ThreadListItemDelete.d.ts +2 -3
  139. package/dist/primitives/threadListItem/ThreadListItemDelete.d.ts.map +1 -1
  140. package/dist/primitives/threadListItem/ThreadListItemRoot.d.ts +2 -3
  141. package/dist/primitives/threadListItem/ThreadListItemRoot.d.ts.map +1 -1
  142. package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts +2 -3
  143. package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts.map +1 -1
  144. package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts +2 -3
  145. package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts.map +1 -1
  146. package/package.json +13 -11
  147. package/src/index.ts +30 -0
  148. package/src/primitives/accessibilityRole.test.tsx +20 -0
  149. package/src/primitives/actionBar/ActionBar.test.tsx +299 -0
  150. package/src/primitives/actionBar/ActionBarCopy.tsx +8 -3
  151. package/src/primitives/actionBar/ActionBarEdit.tsx +16 -4
  152. package/src/primitives/actionBar/ActionBarFeedbackNegative.tsx +14 -3
  153. package/src/primitives/actionBar/ActionBarFeedbackPositive.tsx +14 -3
  154. package/src/primitives/actionBar/ActionBarReload.tsx +16 -4
  155. package/src/primitives/actionBar/ActionBarSpeak.tsx +42 -0
  156. package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +42 -0
  157. package/src/primitives/actionBar.ts +8 -0
  158. package/src/primitives/branchPicker/BranchPickerNext.test.tsx +52 -3
  159. package/src/primitives/branchPicker/BranchPickerNext.tsx +16 -4
  160. package/src/primitives/branchPicker/BranchPickerPrevious.test.tsx +52 -6
  161. package/src/primitives/branchPicker/BranchPickerPrevious.tsx +16 -4
  162. package/src/primitives/composer/ComposerAddAttachment.tsx +3 -8
  163. package/src/primitives/messagePart/MessagePartImage.test.tsx +91 -0
  164. package/src/primitives/messagePart/MessagePartImage.tsx +33 -0
  165. package/src/primitives/messagePart/MessagePartInProgress.ts +1 -0
  166. package/src/primitives/messagePart/MessagePartText.test.tsx +80 -0
  167. package/src/primitives/messagePart/MessagePartText.tsx +36 -0
  168. package/src/primitives/messagePart.ts +10 -0
  169. package/src/primitives/thread/ThreadMessages.test.tsx +335 -54
  170. package/src/primitives/thread/ThreadMessages.tsx +104 -26
@@ -0,0 +1,299 @@
1
+ import { act, type ReactNode } from "react";
2
+ import { createRoot, type Root } from "react-dom/client";
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { ActionBarCopy } from "./ActionBarCopy";
5
+ import { ActionBarEdit } from "./ActionBarEdit";
6
+ import { ActionBarFeedbackNegative } from "./ActionBarFeedbackNegative";
7
+ import { ActionBarFeedbackPositive } from "./ActionBarFeedbackPositive";
8
+ import { ActionBarReload } from "./ActionBarReload";
9
+ import { ActionBarSpeak } from "./ActionBarSpeak";
10
+ import { ActionBarStopSpeaking } from "./ActionBarStopSpeaking";
11
+
12
+ const h = vi.hoisted(() => ({
13
+ copy: vi.fn<() => void>(),
14
+ edit: vi.fn<() => void>(),
15
+ feedbackNegative: vi.fn<() => void>(),
16
+ feedbackPositive: vi.fn<() => void>(),
17
+ reload: vi.fn<() => void>(),
18
+ speak: vi.fn<() => Promise<void>>(),
19
+ stopSpeaking: vi.fn<() => void>(),
20
+ state: {
21
+ copy: { disabled: false, isCopied: false },
22
+ edit: { disabled: false },
23
+ feedbackNegative: { isSubmitted: false },
24
+ feedbackPositive: { isSubmitted: false },
25
+ reload: { disabled: false },
26
+ speak: { disabled: false },
27
+ stopSpeaking: { disabled: false },
28
+ },
29
+ }));
30
+
31
+ vi.mock("@assistant-ui/core/react", async (importOriginal) => {
32
+ const actual =
33
+ await importOriginal<typeof import("@assistant-ui/core/react")>();
34
+ return {
35
+ ...actual,
36
+ useActionBarCopy: () => ({
37
+ copy: h.copy,
38
+ disabled: h.state.copy.disabled,
39
+ isCopied: h.state.copy.isCopied,
40
+ }),
41
+ useActionBarEdit: () => ({ edit: h.edit, disabled: h.state.edit.disabled }),
42
+ useActionBarFeedbackNegative: () => ({
43
+ submit: h.feedbackNegative,
44
+ isSubmitted: h.state.feedbackNegative.isSubmitted,
45
+ }),
46
+ useActionBarFeedbackPositive: () => ({
47
+ submit: h.feedbackPositive,
48
+ isSubmitted: h.state.feedbackPositive.isSubmitted,
49
+ }),
50
+ useActionBarReload: () => ({
51
+ reload: h.reload,
52
+ disabled: h.state.reload.disabled,
53
+ }),
54
+ useActionBarSpeak: () => ({
55
+ speak: h.speak,
56
+ disabled: h.state.speak.disabled,
57
+ }),
58
+ useActionBarStopSpeaking: () => ({
59
+ stopSpeaking: h.stopSpeaking,
60
+ disabled: h.state.stopSpeaking.disabled,
61
+ }),
62
+ };
63
+ });
64
+
65
+ (globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
66
+
67
+ const click = (el: Element) =>
68
+ el.dispatchEvent(
69
+ new MouseEvent("click", { bubbles: true, cancelable: true }),
70
+ );
71
+
72
+ type DisabledChildren = (state: { disabled: boolean }) => ReactNode;
73
+
74
+ type ActionBarTestCase = {
75
+ name: string;
76
+ disabledState?: { disabled: boolean };
77
+ render: (
78
+ children: ReactNode | DisabledChildren,
79
+ disabled?: boolean,
80
+ ) => ReactNode;
81
+ };
82
+
83
+ const actionBars: ActionBarTestCase[] = [
84
+ {
85
+ name: "ActionBarCopy",
86
+ disabledState: h.state.copy,
87
+ render: (children, disabled) => (
88
+ <ActionBarCopy
89
+ testID="t"
90
+ {...(disabled === undefined ? {} : { disabled })}
91
+ >
92
+ {children}
93
+ </ActionBarCopy>
94
+ ),
95
+ },
96
+ {
97
+ name: "ActionBarEdit",
98
+ disabledState: h.state.edit,
99
+ render: (children, disabled) => (
100
+ <ActionBarEdit
101
+ testID="t"
102
+ {...(disabled === undefined ? {} : { disabled })}
103
+ >
104
+ {children}
105
+ </ActionBarEdit>
106
+ ),
107
+ },
108
+ {
109
+ name: "ActionBarFeedbackNegative",
110
+ render: (children, disabled) => (
111
+ <ActionBarFeedbackNegative
112
+ testID="t"
113
+ {...(disabled === undefined ? {} : { disabled })}
114
+ >
115
+ {children}
116
+ </ActionBarFeedbackNegative>
117
+ ),
118
+ },
119
+ {
120
+ name: "ActionBarFeedbackPositive",
121
+ render: (children, disabled) => (
122
+ <ActionBarFeedbackPositive
123
+ testID="t"
124
+ {...(disabled === undefined ? {} : { disabled })}
125
+ >
126
+ {children}
127
+ </ActionBarFeedbackPositive>
128
+ ),
129
+ },
130
+ {
131
+ name: "ActionBarReload",
132
+ disabledState: h.state.reload,
133
+ render: (children, disabled) => (
134
+ <ActionBarReload
135
+ testID="t"
136
+ {...(disabled === undefined ? {} : { disabled })}
137
+ >
138
+ {children}
139
+ </ActionBarReload>
140
+ ),
141
+ },
142
+ {
143
+ name: "ActionBarSpeak",
144
+ disabledState: h.state.speak,
145
+ render: (children, disabled) => (
146
+ <ActionBarSpeak
147
+ testID="t"
148
+ {...(disabled === undefined ? {} : { disabled })}
149
+ >
150
+ {children}
151
+ </ActionBarSpeak>
152
+ ),
153
+ },
154
+ {
155
+ name: "ActionBarStopSpeaking",
156
+ disabledState: h.state.stopSpeaking,
157
+ render: (children, disabled) => (
158
+ <ActionBarStopSpeaking
159
+ testID="t"
160
+ {...(disabled === undefined ? {} : { disabled })}
161
+ >
162
+ {children}
163
+ </ActionBarStopSpeaking>
164
+ ),
165
+ },
166
+ ];
167
+
168
+ describe("ActionBar", () => {
169
+ let container: HTMLDivElement;
170
+ let root: Root;
171
+
172
+ beforeEach(() => {
173
+ h.copy.mockReset();
174
+ h.edit.mockReset();
175
+ h.feedbackNegative.mockReset();
176
+ h.feedbackPositive.mockReset();
177
+ h.reload.mockReset();
178
+ h.speak.mockReset();
179
+ h.stopSpeaking.mockReset();
180
+ h.state.copy.disabled = false;
181
+ h.state.copy.isCopied = false;
182
+ h.state.edit.disabled = false;
183
+ h.state.feedbackNegative.isSubmitted = false;
184
+ h.state.feedbackPositive.isSubmitted = false;
185
+ h.state.reload.disabled = false;
186
+ h.state.speak.disabled = false;
187
+ h.state.stopSpeaking.disabled = false;
188
+
189
+ container = document.createElement("div");
190
+ document.body.appendChild(container);
191
+ root = createRoot(container);
192
+ });
193
+
194
+ afterEach(async () => {
195
+ await act(async () => {
196
+ root.unmount();
197
+ });
198
+ container.remove();
199
+ });
200
+
201
+ const mount = async (node: ReactNode) => {
202
+ await act(async () => {
203
+ root.render(node);
204
+ });
205
+ return container.querySelector('[data-testid="t"]') as HTMLElement;
206
+ };
207
+
208
+ for (const actionBar of actionBars) {
209
+ describe(actionBar.name, () => {
210
+ it("passes the effective disabled state to render children", async () => {
211
+ if (actionBar.disabledState) {
212
+ actionBar.disabledState.disabled = true;
213
+ }
214
+
215
+ let initialState: { disabled: boolean } | null = null;
216
+ const initialEl = await mount(
217
+ actionBar.render((state) => {
218
+ initialState = state;
219
+ return state.disabled ? "disabled" : "enabled";
220
+ }),
221
+ );
222
+
223
+ expect(initialEl.textContent).toBe(
224
+ actionBar.disabledState ? "disabled" : "enabled",
225
+ );
226
+ expect(initialState).toMatchObject({
227
+ disabled: actionBar.disabledState ? true : false,
228
+ });
229
+
230
+ let propState: { disabled: boolean } | null = null;
231
+ const propEl = await mount(
232
+ actionBar.render((state) => {
233
+ propState = state;
234
+ return state.disabled ? "disabled" : "enabled";
235
+ }, !actionBar.disabledState),
236
+ );
237
+
238
+ expect(propEl.textContent).toBe(
239
+ actionBar.disabledState ? "enabled" : "disabled",
240
+ );
241
+ expect(propState).toMatchObject({
242
+ disabled: !actionBar.disabledState,
243
+ });
244
+ });
245
+
246
+ it("renders plain children", async () => {
247
+ const el = await mount(actionBar.render("label"));
248
+
249
+ expect(el.textContent).toBe("label");
250
+ });
251
+ });
252
+ }
253
+
254
+ it("calls speak on press", async () => {
255
+ const el = await mount(<ActionBarSpeak testID="t">speak</ActionBarSpeak>);
256
+
257
+ await act(async () => {
258
+ click(el);
259
+ });
260
+
261
+ expect(h.speak).toHaveBeenCalledTimes(1);
262
+ });
263
+
264
+ it("does not call speak while the hook disables it", async () => {
265
+ h.state.speak.disabled = true;
266
+ const el = await mount(<ActionBarSpeak testID="t">speak</ActionBarSpeak>);
267
+
268
+ await act(async () => {
269
+ click(el);
270
+ });
271
+
272
+ expect(h.speak).not.toHaveBeenCalled();
273
+ });
274
+
275
+ it("calls stopSpeaking on press", async () => {
276
+ const el = await mount(
277
+ <ActionBarStopSpeaking testID="t">stop</ActionBarStopSpeaking>,
278
+ );
279
+
280
+ await act(async () => {
281
+ click(el);
282
+ });
283
+
284
+ expect(h.stopSpeaking).toHaveBeenCalledTimes(1);
285
+ });
286
+
287
+ it("does not call stopSpeaking while the hook disables it", async () => {
288
+ h.state.stopSpeaking.disabled = true;
289
+ const el = await mount(
290
+ <ActionBarStopSpeaking testID="t">stop</ActionBarStopSpeaking>,
291
+ );
292
+
293
+ await act(async () => {
294
+ click(el);
295
+ });
296
+
297
+ expect(h.stopSpeaking).not.toHaveBeenCalled();
298
+ });
299
+ });
@@ -7,7 +7,9 @@ import {
7
7
 
8
8
  export type ActionBarCopyProps = Omit<PressableProps, "onPress" | "children"> &
9
9
  UseActionBarCopyOptions & {
10
- children: ReactNode | ((props: { isCopied: boolean }) => ReactNode);
10
+ children:
11
+ | ReactNode
12
+ | ((props: { isCopied: boolean; disabled: boolean }) => ReactNode);
11
13
  };
12
14
 
13
15
  export const ActionBarCopy = ({
@@ -21,15 +23,18 @@ export const ActionBarCopy = ({
21
23
  copiedDuration,
22
24
  copyToClipboard,
23
25
  });
26
+ const isDisabled = disabledProp ?? disabled;
24
27
 
25
28
  return (
26
29
  <Pressable
27
30
  onPress={copy}
28
- disabled={disabledProp ?? disabled}
31
+ disabled={isDisabled}
29
32
  accessibilityRole="button"
30
33
  {...pressableProps}
31
34
  >
32
- {typeof children === "function" ? children({ isCopied }) : children}
35
+ {typeof children === "function"
36
+ ? children({ isCopied, disabled: isDisabled })
37
+ : children}
33
38
  </Pressable>
34
39
  );
35
40
  };
@@ -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 { useActionBarEdit } from "@assistant-ui/core/react";
3
8
 
4
9
  export type ActionBarEditProps = 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 ActionBarEdit = ({
@@ -14,15 +23,18 @@ export const ActionBarEdit = ({
14
23
  ...pressableProps
15
24
  }: ActionBarEditProps) => {
16
25
  const { edit, disabled } = useActionBarEdit();
26
+ const isDisabled = disabledProp ?? disabled;
17
27
 
18
28
  return (
19
29
  <Pressable
20
30
  onPress={edit}
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
  };
@@ -6,18 +6,29 @@ export type ActionBarFeedbackNegativeProps = Omit<
6
6
  PressableProps,
7
7
  "onPress" | "children"
8
8
  > & {
9
- children: ReactNode | ((props: { isSubmitted: boolean }) => ReactNode);
9
+ children:
10
+ | ReactNode
11
+ | ((props: { isSubmitted: boolean; disabled: boolean }) => ReactNode);
10
12
  };
11
13
 
12
14
  export const ActionBarFeedbackNegative = ({
13
15
  children,
16
+ disabled: disabledProp,
14
17
  ...pressableProps
15
18
  }: ActionBarFeedbackNegativeProps) => {
16
19
  const { submit, isSubmitted } = useActionBarFeedbackNegative();
20
+ const disabled = disabledProp ?? false;
17
21
 
18
22
  return (
19
- <Pressable onPress={submit} accessibilityRole="button" {...pressableProps}>
20
- {typeof children === "function" ? children({ isSubmitted }) : children}
23
+ <Pressable
24
+ onPress={submit}
25
+ disabled={disabledProp}
26
+ accessibilityRole="button"
27
+ {...pressableProps}
28
+ >
29
+ {typeof children === "function"
30
+ ? children({ isSubmitted, disabled })
31
+ : children}
21
32
  </Pressable>
22
33
  );
23
34
  };
@@ -6,18 +6,29 @@ export type ActionBarFeedbackPositiveProps = Omit<
6
6
  PressableProps,
7
7
  "onPress" | "children"
8
8
  > & {
9
- children: ReactNode | ((props: { isSubmitted: boolean }) => ReactNode);
9
+ children:
10
+ | ReactNode
11
+ | ((props: { isSubmitted: boolean; disabled: boolean }) => ReactNode);
10
12
  };
11
13
 
12
14
  export const ActionBarFeedbackPositive = ({
13
15
  children,
16
+ disabled: disabledProp,
14
17
  ...pressableProps
15
18
  }: ActionBarFeedbackPositiveProps) => {
16
19
  const { submit, isSubmitted } = useActionBarFeedbackPositive();
20
+ const disabled = disabledProp ?? false;
17
21
 
18
22
  return (
19
- <Pressable onPress={submit} accessibilityRole="button" {...pressableProps}>
20
- {typeof children === "function" ? children({ isSubmitted }) : children}
23
+ <Pressable
24
+ onPress={submit}
25
+ disabled={disabledProp}
26
+ accessibilityRole="button"
27
+ {...pressableProps}
28
+ >
29
+ {typeof children === "function"
30
+ ? children({ isSubmitted, disabled })
31
+ : children}
21
32
  </Pressable>
22
33
  );
23
34
  };
@@ -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 { useActionBarReload } from "@assistant-ui/core/react";
3
8
 
4
9
  export type ActionBarReloadProps = 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 ActionBarReload = ({
@@ -14,15 +23,18 @@ export const ActionBarReload = ({
14
23
  ...pressableProps
15
24
  }: ActionBarReloadProps) => {
16
25
  const { reload, disabled } = useActionBarReload();
26
+ const isDisabled = disabledProp ?? disabled;
17
27
 
18
28
  return (
19
29
  <Pressable
20
30
  onPress={reload}
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,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
  };