@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.
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/primitives/actionBar/ActionBarCopy.d.ts +3 -3
- package/dist/primitives/actionBar/ActionBarCopy.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarCopy.js +23 -19
- package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarEdit.d.ts +7 -5
- package/dist/primitives/actionBar/ActionBarEdit.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarEdit.js +21 -11
- package/dist/primitives/actionBar/ActionBarEdit.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.d.ts +3 -3
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +26 -16
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.d.ts +3 -3
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +26 -16
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarReload.d.ts +7 -5
- package/dist/primitives/actionBar/ActionBarReload.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarReload.js +21 -11
- package/dist/primitives/actionBar/ActionBarReload.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarSpeak.d.ts +14 -0
- package/dist/primitives/actionBar/ActionBarSpeak.d.ts.map +1 -0
- package/dist/primitives/actionBar/ActionBarSpeak.js +61 -0
- package/dist/primitives/actionBar/ActionBarSpeak.js.map +1 -0
- package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts +14 -0
- package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts.map +1 -0
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js +61 -0
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -0
- package/dist/primitives/actionBar.d.ts +4 -2
- package/dist/primitives/actionBar.js +6 -2
- package/dist/primitives/actionBar.js.map +1 -1
- package/dist/primitives/attachment/AttachmentName.d.ts +2 -3
- package/dist/primitives/attachment/AttachmentName.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentRemove.d.ts +2 -3
- package/dist/primitives/attachment/AttachmentRemove.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentRoot.d.ts +2 -3
- package/dist/primitives/attachment/AttachmentRoot.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentThumb.d.ts +2 -3
- package/dist/primitives/attachment/AttachmentThumb.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerCount.d.ts +2 -3
- package/dist/primitives/branchPicker/BranchPickerCount.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerNext.d.ts +7 -5
- package/dist/primitives/branchPicker/BranchPickerNext.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerNext.js +21 -11
- package/dist/primitives/branchPicker/BranchPickerNext.js.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerNumber.d.ts +2 -3
- package/dist/primitives/branchPicker/BranchPickerNumber.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerPrevious.d.ts +7 -5
- package/dist/primitives/branchPicker/BranchPickerPrevious.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerPrevious.js +21 -11
- package/dist/primitives/branchPicker/BranchPickerPrevious.js.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.d.ts +2 -3
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.d.ts.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtRoot.d.ts +2 -3
- package/dist/primitives/chainOfThought/ChainOfThoughtRoot.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.d.ts +2 -3
- package/dist/primitives/composer/ComposerAddAttachment.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAttachments.d.ts +4 -5
- package/dist/primitives/composer/ComposerAttachments.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerCancel.d.ts +2 -3
- package/dist/primitives/composer/ComposerCancel.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerInput.d.ts +2 -3
- package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerQuote.d.ts +2 -3
- package/dist/primitives/composer/ComposerQuote.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerQuoteDismiss.d.ts +2 -3
- package/dist/primitives/composer/ComposerQuoteDismiss.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerQuoteText.d.ts +2 -3
- package/dist/primitives/composer/ComposerQuoteText.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerRoot.d.ts +2 -3
- package/dist/primitives/composer/ComposerRoot.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerSend.d.ts +2 -3
- package/dist/primitives/composer/ComposerSend.d.ts.map +1 -1
- package/dist/primitives/error/ErrorMessage.d.ts +2 -3
- package/dist/primitives/error/ErrorMessage.d.ts.map +1 -1
- package/dist/primitives/error/ErrorRoot.d.ts +2 -3
- package/dist/primitives/error/ErrorRoot.d.ts.map +1 -1
- package/dist/primitives/message/MessageAttachments.d.ts +4 -5
- package/dist/primitives/message/MessageAttachments.d.ts.map +1 -1
- package/dist/primitives/message/MessageContent.d.ts +2 -3
- package/dist/primitives/message/MessageContent.d.ts.map +1 -1
- package/dist/primitives/message/MessageIf.d.ts +2 -3
- package/dist/primitives/message/MessageIf.d.ts.map +1 -1
- package/dist/primitives/message/MessageParts.d.ts +3 -3
- package/dist/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/primitives/message/MessageRoot.d.ts +2 -3
- package/dist/primitives/message/MessageRoot.d.ts.map +1 -1
- package/dist/primitives/messagePart/MessagePartImage.d.ts +11 -0
- package/dist/primitives/messagePart/MessagePartImage.d.ts.map +1 -0
- package/dist/primitives/messagePart/MessagePartImage.js +39 -0
- package/dist/primitives/messagePart/MessagePartImage.js.map +1 -0
- package/dist/primitives/messagePart/MessagePartInProgress.d.ts +2 -0
- package/dist/primitives/messagePart/MessagePartInProgress.js +2 -0
- package/dist/primitives/messagePart/MessagePartText.d.ts +11 -0
- package/dist/primitives/messagePart/MessagePartText.d.ts.map +1 -0
- package/dist/primitives/messagePart/MessagePartText.js +32 -0
- package/dist/primitives/messagePart/MessagePartText.js.map +1 -0
- package/dist/primitives/messagePart.d.ts +10 -0
- package/dist/primitives/messagePart.d.ts.map +1 -0
- package/dist/primitives/messagePart.js +16 -0
- package/dist/primitives/messagePart.js.map +1 -0
- package/dist/primitives/queueItem/QueueItemRemove.d.ts +2 -3
- package/dist/primitives/queueItem/QueueItemRemove.d.ts.map +1 -1
- package/dist/primitives/queueItem/QueueItemSteer.d.ts +2 -3
- package/dist/primitives/queueItem/QueueItemSteer.d.ts.map +1 -1
- package/dist/primitives/queueItem/QueueItemText.d.ts +2 -3
- package/dist/primitives/queueItem/QueueItemText.d.ts.map +1 -1
- package/dist/primitives/suggestion/SuggestionDescription.d.ts +2 -3
- package/dist/primitives/suggestion/SuggestionDescription.d.ts.map +1 -1
- package/dist/primitives/suggestion/SuggestionTitle.d.ts +2 -3
- package/dist/primitives/suggestion/SuggestionTitle.d.ts.map +1 -1
- package/dist/primitives/suggestion/SuggestionTrigger.d.ts +2 -3
- package/dist/primitives/suggestion/SuggestionTrigger.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadEmpty.d.ts +2 -3
- package/dist/primitives/thread/ThreadEmpty.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadIf.d.ts +2 -3
- package/dist/primitives/thread/ThreadIf.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadMessages.d.ts +9 -5
- package/dist/primitives/thread/ThreadMessages.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadMessages.js +117 -78
- package/dist/primitives/thread/ThreadMessages.js.map +1 -1
- package/dist/primitives/thread/ThreadRoot.d.ts +2 -3
- package/dist/primitives/thread/ThreadRoot.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadSuggestion.d.ts +2 -3
- package/dist/primitives/thread/ThreadSuggestion.d.ts.map +1 -1
- package/dist/primitives/threadList/ThreadListItems.d.ts +2 -3
- package/dist/primitives/threadList/ThreadListItems.d.ts.map +1 -1
- package/dist/primitives/threadList/ThreadListNew.d.ts +2 -3
- package/dist/primitives/threadList/ThreadListNew.d.ts.map +1 -1
- package/dist/primitives/threadList/ThreadListRoot.d.ts +2 -3
- package/dist/primitives/threadList/ThreadListRoot.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemArchive.d.ts +2 -3
- package/dist/primitives/threadListItem/ThreadListItemArchive.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemDelete.d.ts +2 -3
- package/dist/primitives/threadListItem/ThreadListItemDelete.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemRoot.d.ts +2 -3
- package/dist/primitives/threadListItem/ThreadListItemRoot.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts +2 -3
- package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts +2 -3
- package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts.map +1 -1
- package/package.json +13 -12
- package/src/index.ts +1 -0
- package/src/primitives/accessibilityRole.test.tsx +20 -0
- package/src/primitives/actionBar/ActionBar.test.tsx +299 -0
- package/src/primitives/actionBar/ActionBarCopy.tsx +8 -3
- package/src/primitives/actionBar/ActionBarEdit.tsx +16 -4
- package/src/primitives/actionBar/ActionBarFeedbackNegative.tsx +14 -3
- package/src/primitives/actionBar/ActionBarFeedbackPositive.tsx +14 -3
- package/src/primitives/actionBar/ActionBarReload.tsx +16 -4
- package/src/primitives/actionBar/ActionBarSpeak.tsx +42 -0
- package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +42 -0
- package/src/primitives/actionBar.ts +8 -0
- package/src/primitives/branchPicker/BranchPickerNext.test.tsx +52 -3
- package/src/primitives/branchPicker/BranchPickerNext.tsx +16 -4
- package/src/primitives/branchPicker/BranchPickerPrevious.test.tsx +52 -6
- package/src/primitives/branchPicker/BranchPickerPrevious.tsx +16 -4
- package/src/primitives/messagePart/MessagePartImage.test.tsx +91 -0
- package/src/primitives/messagePart/MessagePartImage.tsx +33 -0
- package/src/primitives/messagePart/MessagePartInProgress.ts +1 -0
- package/src/primitives/messagePart/MessagePartText.test.tsx +80 -0
- package/src/primitives/messagePart/MessagePartText.tsx +36 -0
- package/src/primitives/messagePart.ts +10 -0
- package/src/primitives/thread/ThreadMessages.test.tsx +79 -1
- 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
|
-
|
|
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 {
|
|
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:
|
|
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={
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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 {
|
|
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:
|
|
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={
|
|
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" }];
|