@assistant-ui/react-native 0.1.37 → 0.1.39
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 +4 -3
- package/dist/index.js +3 -2
- package/dist/primitives/actionBar/ActionBarCopy.js +1 -0
- package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarEdit.d.ts +2 -3
- package/dist/primitives/actionBar/ActionBarEdit.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarEdit.js +1 -0
- package/dist/primitives/actionBar/ActionBarEdit.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +1 -0
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +1 -0
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarReload.d.ts +2 -3
- package/dist/primitives/actionBar/ActionBarReload.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarReload.js +1 -0
- package/dist/primitives/actionBar/ActionBarReload.js.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/AttachmentRemove.js +1 -0
- package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
- package/dist/primitives/attachment/AttachmentRoot.js.map +1 -1
- package/dist/primitives/attachment/AttachmentThumb.js.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerNext.d.ts +2 -3
- package/dist/primitives/branchPicker/BranchPickerNext.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerNext.js +1 -0
- package/dist/primitives/branchPicker/BranchPickerNext.js.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerPrevious.d.ts +2 -3
- package/dist/primitives/branchPicker/BranchPickerPrevious.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerPrevious.js +1 -0
- 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/ChainOfThoughtAccordionTrigger.js +1 -0
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtRoot.js.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/ComposerAddAttachment.js +1 -0
- package/dist/primitives/composer/ComposerAddAttachment.js.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/ComposerCancel.js +1 -0
- package/dist/primitives/composer/ComposerCancel.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +2 -0
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerQueue.d.ts +2 -0
- package/dist/primitives/composer/ComposerQueue.js +2 -0
- package/dist/primitives/composer/ComposerQuote.d.ts +10 -0
- package/dist/primitives/composer/ComposerQuote.d.ts.map +1 -0
- package/dist/primitives/composer/ComposerQuote.js +38 -0
- package/dist/primitives/composer/ComposerQuote.js.map +1 -0
- package/dist/primitives/composer/ComposerQuoteDismiss.d.ts +9 -0
- package/dist/primitives/composer/ComposerQuoteDismiss.d.ts.map +1 -0
- package/dist/primitives/composer/ComposerQuoteDismiss.js +48 -0
- package/dist/primitives/composer/ComposerQuoteDismiss.js.map +1 -0
- package/dist/primitives/composer/ComposerQuoteText.d.ts +10 -0
- package/dist/primitives/composer/ComposerQuoteText.d.ts.map +1 -0
- package/dist/primitives/composer/ComposerQuoteText.js +40 -0
- package/dist/primitives/composer/ComposerQuoteText.js.map +1 -0
- package/dist/primitives/composer/ComposerRoot.js.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/composer/ComposerSend.js +1 -0
- package/dist/primitives/composer/ComposerSend.js.map +1 -1
- package/dist/primitives/composer.d.ts +6 -2
- package/dist/primitives/composer.js +9 -1
- package/dist/primitives/composer.js.map +1 -1
- package/dist/primitives/error/ErrorMessage.js.map +1 -1
- package/dist/primitives/error/ErrorRoot.js.map +1 -1
- package/dist/primitives/message/MessageIf.d.ts +3 -0
- package/dist/primitives/message/MessageIf.d.ts.map +1 -1
- package/dist/primitives/message/MessageIf.js +3 -0
- package/dist/primitives/message/MessageIf.js.map +1 -1
- package/dist/primitives/message/MessageParts.js.map +1 -1
- package/dist/primitives/message/MessageRoot.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemRemove.d.ts +9 -0
- package/dist/primitives/queueItem/QueueItemRemove.d.ts.map +1 -0
- package/dist/primitives/queueItem/QueueItemRemove.js +48 -0
- package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -0
- package/dist/primitives/queueItem/QueueItemSteer.d.ts +9 -0
- package/dist/primitives/queueItem/QueueItemSteer.d.ts.map +1 -0
- package/dist/primitives/queueItem/QueueItemSteer.js +48 -0
- package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -0
- package/dist/primitives/queueItem/QueueItemText.d.ts +10 -0
- package/dist/primitives/queueItem/QueueItemText.d.ts.map +1 -0
- package/dist/primitives/queueItem/QueueItemText.js +45 -0
- package/dist/primitives/queueItem/QueueItemText.js.map +1 -0
- package/dist/primitives/queueItem.d.ts +9 -0
- package/dist/primitives/queueItem.d.ts.map +1 -0
- package/dist/primitives/queueItem.js +14 -0
- package/dist/primitives/queueItem.js.map +1 -0
- package/dist/primitives/suggestion/SuggestionDescription.js.map +1 -1
- package/dist/primitives/suggestion/SuggestionTitle.js.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/suggestion/SuggestionTrigger.js +1 -0
- package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
- package/dist/primitives/thread/ThreadEmpty.d.ts +4 -1
- package/dist/primitives/thread/ThreadEmpty.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadEmpty.js +12 -4
- package/dist/primitives/thread/ThreadEmpty.js.map +1 -1
- package/dist/primitives/thread/ThreadIf.d.ts +3 -0
- package/dist/primitives/thread/ThreadIf.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadIf.js +5 -6
- package/dist/primitives/thread/ThreadIf.js.map +1 -1
- package/dist/primitives/thread/ThreadMessages.js.map +1 -1
- package/dist/primitives/thread/ThreadRoot.js.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/thread/ThreadSuggestion.js +1 -0
- package/dist/primitives/thread/ThreadSuggestion.js.map +1 -1
- package/dist/primitives/threadList/ThreadListItems.js.map +1 -1
- package/dist/primitives/threadList/ThreadListNew.d.ts +6 -4
- package/dist/primitives/threadList/ThreadListNew.d.ts.map +1 -1
- package/dist/primitives/threadList/ThreadListNew.js +46 -15
- package/dist/primitives/threadList/ThreadListNew.js.map +1 -1
- package/dist/primitives/threadList/ThreadListRoot.js.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/ThreadListItemArchive.js +1 -0
- package/dist/primitives/threadListItem/ThreadListItemArchive.js.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/ThreadListItemDelete.js +1 -0
- package/dist/primitives/threadListItem/ThreadListItemDelete.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemRoot.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts +6 -4
- package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js +46 -15
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts +2 -3
- package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemUnarchive.js +1 -0
- package/dist/primitives/threadListItem/ThreadListItemUnarchive.js.map +1 -1
- package/package.json +9 -9
- package/src/index.ts +5 -0
- package/src/primitives/accessibilityRole.test.tsx +335 -0
- package/src/primitives/actionBar/ActionBarCopy.tsx +1 -0
- package/src/primitives/actionBar/ActionBarEdit.tsx +6 -3
- package/src/primitives/actionBar/ActionBarFeedbackNegative.tsx +1 -1
- package/src/primitives/actionBar/ActionBarFeedbackPositive.tsx +1 -1
- package/src/primitives/actionBar/ActionBarReload.tsx +6 -3
- package/src/primitives/attachment/AttachmentRemove.tsx +11 -4
- package/src/primitives/branchPicker/BranchPickerNext.tsx +6 -3
- package/src/primitives/branchPicker/BranchPickerPrevious.tsx +6 -3
- package/src/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.tsx +4 -4
- package/src/primitives/composer/ComposerAddAttachment.tsx +10 -4
- package/src/primitives/composer/ComposerCancel.tsx +6 -3
- package/src/primitives/composer/ComposerInput.test.tsx +47 -1
- package/src/primitives/composer/ComposerInput.tsx +2 -0
- package/src/primitives/composer/ComposerQueue.test.tsx +9 -0
- package/src/primitives/composer/ComposerQueue.tsx +1 -0
- package/src/primitives/composer/ComposerQuote.test.tsx +163 -0
- package/src/primitives/composer/ComposerQuote.tsx +17 -0
- package/src/primitives/composer/ComposerQuoteDismiss.tsx +31 -0
- package/src/primitives/composer/ComposerQuoteText.tsx +17 -0
- package/src/primitives/composer/ComposerSend.test.tsx +26 -0
- package/src/primitives/composer/ComposerSend.tsx +3 -3
- package/src/primitives/composer.ts +13 -0
- package/src/primitives/message/MessageIf.tsx +3 -0
- package/src/primitives/queueItem/QueueItem.test.tsx +132 -0
- package/src/primitives/queueItem/QueueItemRemove.tsx +31 -0
- package/src/primitives/queueItem/QueueItemSteer.tsx +31 -0
- package/src/primitives/queueItem/QueueItemText.tsx +22 -0
- package/src/primitives/queueItem.ts +12 -0
- package/src/primitives/suggestion/SuggestionTrigger.tsx +6 -3
- package/src/primitives/thread/ThreadEmpty.test.tsx +17 -3
- package/src/primitives/thread/ThreadEmpty.tsx +7 -6
- package/src/primitives/thread/ThreadIf.test.tsx +20 -20
- package/src/primitives/thread/ThreadIf.tsx +5 -8
- package/src/primitives/thread/ThreadSuggestion.tsx +6 -3
- package/src/primitives/threadList/ThreadListNew.test.tsx +118 -3
- package/src/primitives/threadList/ThreadListNew.tsx +28 -5
- package/src/primitives/threadListItem/ThreadListItemArchive.tsx +6 -4
- package/src/primitives/threadListItem/ThreadListItemDelete.tsx +10 -4
- package/src/primitives/threadListItem/ThreadListItemTrigger.test.tsx +74 -0
- package/src/primitives/threadListItem/ThreadListItemTrigger.tsx +28 -5
- package/src/primitives/threadListItem/ThreadListItemUnarchive.tsx +10 -4
- package/dist/runtimes/RemoteThreadListHookInstanceManager.d.ts +0 -2
- package/dist/runtimes/RemoteThreadListHookInstanceManager.js +0 -2
- package/dist/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +0 -2
- package/dist/runtimes/RemoteThreadListThreadListRuntimeCore.js +0 -2
- package/src/runtimes/RemoteThreadListHookInstanceManager.tsx +0 -1
- package/src/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +0 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { act, type ReactElement } from "react";
|
|
2
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { Text } from "react-native";
|
|
5
|
+
import { QueueItemRemove } from "./QueueItemRemove";
|
|
6
|
+
import { QueueItemSteer } from "./QueueItemSteer";
|
|
7
|
+
import { QueueItemText } from "./QueueItemText";
|
|
8
|
+
|
|
9
|
+
const h = vi.hoisted(() => ({
|
|
10
|
+
parts: [] as Array<{ type: "text" | "file"; text?: string }> | undefined,
|
|
11
|
+
remove: vi.fn<() => void>(),
|
|
12
|
+
steer: vi.fn<() => void>(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => {
|
|
16
|
+
const actual = await importOriginal<typeof import("@assistant-ui/store")>();
|
|
17
|
+
return {
|
|
18
|
+
...actual,
|
|
19
|
+
useAui: () => ({ queueItem: { remove: h.remove, steer: h.steer } }),
|
|
20
|
+
useAuiState: <T,>(selector: (state: { queueItem: typeof h }) => T) =>
|
|
21
|
+
selector({ queueItem: h }),
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
26
|
+
|
|
27
|
+
describe("Queue item primitives", () => {
|
|
28
|
+
let container: HTMLDivElement;
|
|
29
|
+
let root: Root;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
h.parts = [];
|
|
33
|
+
h.remove.mockReset();
|
|
34
|
+
h.steer.mockReset();
|
|
35
|
+
container = document.createElement("div");
|
|
36
|
+
document.body.appendChild(container);
|
|
37
|
+
root = createRoot(container);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterEach(async () => {
|
|
41
|
+
await act(async () => {
|
|
42
|
+
root.unmount();
|
|
43
|
+
});
|
|
44
|
+
container.remove();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const render = async (element: ReactElement) => {
|
|
48
|
+
await act(async () => {
|
|
49
|
+
root.render(element);
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const press = async (testID: string) => {
|
|
54
|
+
const element = container.querySelector(`[data-testid="${testID}"]`);
|
|
55
|
+
expect(element).not.toBeNull();
|
|
56
|
+
await act(async () => {
|
|
57
|
+
element!.dispatchEvent(
|
|
58
|
+
new MouseEvent("click", { bubbles: true, cancelable: true }),
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
it("renders only text parts and supports child overrides", async () => {
|
|
64
|
+
h.parts = [
|
|
65
|
+
{ type: "text", text: "hello" },
|
|
66
|
+
{ type: "file", text: "not rendered" },
|
|
67
|
+
{ type: "text", text: "world" },
|
|
68
|
+
];
|
|
69
|
+
await render(<QueueItemText testID="text" />);
|
|
70
|
+
expect(container.querySelector('[data-testid="text"]')?.textContent).toBe(
|
|
71
|
+
"hello\n\nworld",
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
await render(
|
|
75
|
+
<QueueItemText testID="text">
|
|
76
|
+
<Text>override</Text>
|
|
77
|
+
</QueueItemText>,
|
|
78
|
+
);
|
|
79
|
+
expect(container.querySelector('[data-testid="text"]')?.textContent).toBe(
|
|
80
|
+
"override",
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("removes a queue item when pressed", async () => {
|
|
85
|
+
await render(<QueueItemRemove testID="remove">remove</QueueItemRemove>);
|
|
86
|
+
await press("remove");
|
|
87
|
+
expect(h.remove).toHaveBeenCalledTimes(1);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("steers a queue item when pressed", async () => {
|
|
91
|
+
await render(<QueueItemSteer testID="steer">steer</QueueItemSteer>);
|
|
92
|
+
await press("steer");
|
|
93
|
+
expect(h.steer).toHaveBeenCalledTimes(1);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("renders nothing extra when queue item parts are missing", async () => {
|
|
97
|
+
h.parts = undefined;
|
|
98
|
+
await render(<QueueItemText testID="text" />);
|
|
99
|
+
expect(container.querySelector('[data-testid="text"]')?.textContent).toBe(
|
|
100
|
+
"",
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("passes the Pressable press state to function children", async () => {
|
|
105
|
+
await render(
|
|
106
|
+
<QueueItemRemove testID="remove">
|
|
107
|
+
{({ pressed }) => (pressed ? "pressed" : "idle")}
|
|
108
|
+
</QueueItemRemove>,
|
|
109
|
+
);
|
|
110
|
+
expect(container.querySelector('[data-testid="remove"]')?.textContent).toBe(
|
|
111
|
+
"idle",
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
await render(
|
|
115
|
+
<QueueItemRemove testID="remove" testOnly_pressed>
|
|
116
|
+
{({ pressed }) => (pressed ? "pressed" : "idle")}
|
|
117
|
+
</QueueItemRemove>,
|
|
118
|
+
);
|
|
119
|
+
expect(container.querySelector('[data-testid="remove"]')?.textContent).toBe(
|
|
120
|
+
"pressed",
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
await render(
|
|
124
|
+
<QueueItemSteer testID="steer" testOnly_pressed>
|
|
125
|
+
{({ pressed }) => (pressed ? "pressed" : "idle")}
|
|
126
|
+
</QueueItemSteer>,
|
|
127
|
+
);
|
|
128
|
+
expect(container.querySelector('[data-testid="steer"]')?.textContent).toBe(
|
|
129
|
+
"pressed",
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { Pressable, type PressableProps } from "react-native";
|
|
3
|
+
import { useAui } from "@assistant-ui/store";
|
|
4
|
+
|
|
5
|
+
export type QueueItemRemoveProps = Omit<
|
|
6
|
+
PressableProps,
|
|
7
|
+
"onPress" | "children"
|
|
8
|
+
> & {
|
|
9
|
+
children: PressableProps["children"];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const QueueItemRemove = ({
|
|
13
|
+
children,
|
|
14
|
+
...pressableProps
|
|
15
|
+
}: QueueItemRemoveProps) => {
|
|
16
|
+
const aui = useAui();
|
|
17
|
+
|
|
18
|
+
const handleRemove = useCallback(() => {
|
|
19
|
+
aui.queueItem.remove();
|
|
20
|
+
}, [aui]);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Pressable
|
|
24
|
+
onPress={handleRemove}
|
|
25
|
+
accessibilityRole="button"
|
|
26
|
+
{...pressableProps}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</Pressable>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { Pressable, type PressableProps } from "react-native";
|
|
3
|
+
import { useAui } from "@assistant-ui/store";
|
|
4
|
+
|
|
5
|
+
export type QueueItemSteerProps = Omit<
|
|
6
|
+
PressableProps,
|
|
7
|
+
"onPress" | "children"
|
|
8
|
+
> & {
|
|
9
|
+
children: PressableProps["children"];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const QueueItemSteer = ({
|
|
13
|
+
children,
|
|
14
|
+
...pressableProps
|
|
15
|
+
}: QueueItemSteerProps) => {
|
|
16
|
+
const aui = useAui();
|
|
17
|
+
|
|
18
|
+
const handleSteer = useCallback(() => {
|
|
19
|
+
aui.queueItem.steer();
|
|
20
|
+
}, [aui]);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Pressable
|
|
24
|
+
onPress={handleSteer}
|
|
25
|
+
accessibilityRole="button"
|
|
26
|
+
{...pressableProps}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</Pressable>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { Text, type TextProps } from "react-native";
|
|
3
|
+
import { useAuiState } from "@assistant-ui/store";
|
|
4
|
+
|
|
5
|
+
export type QueueItemTextProps = TextProps & {
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const QueueItemText = ({
|
|
10
|
+
children,
|
|
11
|
+
...textProps
|
|
12
|
+
}: QueueItemTextProps) => {
|
|
13
|
+
const text = useAuiState((s) =>
|
|
14
|
+
// hosts on the pre-parts adapter shape may omit the field at runtime
|
|
15
|
+
(s.queueItem.parts ?? [])
|
|
16
|
+
.filter((part) => part.type === "text")
|
|
17
|
+
.map((part) => part.text)
|
|
18
|
+
.join("\n\n"),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return <Text {...textProps}>{children ?? text}</Text>;
|
|
22
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export {
|
|
2
|
+
QueueItemText as Text,
|
|
3
|
+
type QueueItemTextProps as TextProps,
|
|
4
|
+
} from "./queueItem/QueueItemText";
|
|
5
|
+
export {
|
|
6
|
+
QueueItemRemove as Remove,
|
|
7
|
+
type QueueItemRemoveProps as RemoveProps,
|
|
8
|
+
} from "./queueItem/QueueItemRemove";
|
|
9
|
+
export {
|
|
10
|
+
QueueItemSteer as Steer,
|
|
11
|
+
type QueueItemSteerProps as SteerProps,
|
|
12
|
+
} from "./queueItem/QueueItemSteer";
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
1
|
import { Pressable, type PressableProps } from "react-native";
|
|
3
2
|
import { useAuiState } from "@assistant-ui/store";
|
|
4
3
|
import { useSuggestionTrigger } from "@assistant-ui/core/react";
|
|
5
4
|
|
|
6
|
-
export type SuggestionTriggerProps = Omit<
|
|
7
|
-
|
|
5
|
+
export type SuggestionTriggerProps = Omit<
|
|
6
|
+
PressableProps,
|
|
7
|
+
"onPress" | "children"
|
|
8
|
+
> & {
|
|
9
|
+
children: PressableProps["children"];
|
|
8
10
|
/**
|
|
9
11
|
* When true, automatically sends the message.
|
|
10
12
|
* When false, replaces or appends the composer text with the suggestion.
|
|
@@ -36,6 +38,7 @@ export const SuggestionTrigger = ({
|
|
|
36
38
|
<Pressable
|
|
37
39
|
onPress={trigger}
|
|
38
40
|
disabled={disabledProp ?? disabled}
|
|
41
|
+
accessibilityRole="button"
|
|
39
42
|
{...pressableProps}
|
|
40
43
|
>
|
|
41
44
|
{children}
|
|
@@ -1,16 +1,30 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
1
2
|
import { act } from "react";
|
|
2
3
|
import { createRoot, type Root } from "react-dom/client";
|
|
3
4
|
import { Text } from "react-native";
|
|
4
5
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import type * as Store from "@assistant-ui/store";
|
|
5
7
|
import { ThreadEmpty } from "./ThreadEmpty";
|
|
6
8
|
|
|
7
9
|
const h = vi.hoisted(() => ({
|
|
8
10
|
isEmpty: true,
|
|
9
11
|
}));
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
type ThreadSlice = { thread: { isEmpty: boolean } };
|
|
14
|
+
|
|
15
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => {
|
|
16
|
+
const actual = await importOriginal<typeof Store>();
|
|
17
|
+
return {
|
|
18
|
+
...actual,
|
|
19
|
+
AuiIf: ({
|
|
20
|
+
condition,
|
|
21
|
+
children,
|
|
22
|
+
}: {
|
|
23
|
+
condition: (s: ThreadSlice) => boolean;
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
}) => (condition({ thread: { isEmpty: h.isEmpty } }) ? children : null),
|
|
26
|
+
};
|
|
27
|
+
});
|
|
14
28
|
|
|
15
29
|
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
16
30
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { AuiIf } from "@assistant-ui/store";
|
|
3
3
|
|
|
4
4
|
export type ThreadEmptyProps = {
|
|
5
5
|
children: ReactNode;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated Use `<AuiIf condition={(s) => s.thread.isEmpty} />` instead.
|
|
10
|
+
*/
|
|
11
|
+
export const ThreadEmpty = ({ children }: ThreadEmptyProps) => (
|
|
12
|
+
<AuiIf condition={(s) => s.thread.isEmpty}>{children}</AuiIf>
|
|
13
|
+
);
|
|
@@ -2,25 +2,29 @@ import { act } from "react";
|
|
|
2
2
|
import { createRoot, type Root } from "react-dom/client";
|
|
3
3
|
import { Text } from "react-native";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import type * as Store from "@assistant-ui/store";
|
|
5
6
|
import { ThreadIf } from "./ThreadIf";
|
|
6
7
|
|
|
7
8
|
const h = vi.hoisted(() => ({
|
|
8
|
-
thread: {
|
|
9
|
+
thread: { isEmpty: true, isRunning: false },
|
|
9
10
|
}));
|
|
10
11
|
|
|
11
|
-
vi.mock("@assistant-ui/store", () =>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => {
|
|
13
|
+
const actual = await importOriginal<typeof Store>();
|
|
14
|
+
return {
|
|
15
|
+
...actual,
|
|
16
|
+
useAuiState: <T,>(selector: (s: { thread: typeof h.thread }) => T) =>
|
|
17
|
+
selector({ thread: h.thread }),
|
|
18
|
+
};
|
|
19
|
+
});
|
|
15
20
|
|
|
16
21
|
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
17
22
|
|
|
18
23
|
describe("ThreadIf", () => {
|
|
19
24
|
let container: HTMLDivElement;
|
|
20
25
|
let root: Root;
|
|
21
|
-
|
|
22
26
|
beforeEach(() => {
|
|
23
|
-
h.thread.
|
|
27
|
+
h.thread.isEmpty = true;
|
|
24
28
|
h.thread.isRunning = false;
|
|
25
29
|
|
|
26
30
|
container = document.createElement("div");
|
|
@@ -46,33 +50,29 @@ describe("ThreadIf", () => {
|
|
|
46
50
|
return container.querySelector('[data-testid="child"]');
|
|
47
51
|
};
|
|
48
52
|
|
|
49
|
-
const setMessages = (count: number) => {
|
|
50
|
-
h.thread.messages = Array.from({ length: count }, (_, i) => i);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
53
|
it("renders children when no guard is set", async () => {
|
|
54
|
-
|
|
54
|
+
h.thread.isEmpty = false;
|
|
55
55
|
expect(await mount()).not.toBeNull();
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
describe("empty guard", () => {
|
|
59
59
|
it("renders children when empty:true matches an empty thread", async () => {
|
|
60
|
-
|
|
60
|
+
h.thread.isEmpty = true;
|
|
61
61
|
expect(await mount({ empty: true })).not.toBeNull();
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
it("hides children when empty:true but the thread
|
|
65
|
-
|
|
64
|
+
it("hides children when empty:true but the thread is not empty", async () => {
|
|
65
|
+
h.thread.isEmpty = false;
|
|
66
66
|
expect(await mount({ empty: true })).toBeNull();
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
it("renders children when empty:false matches a non-empty thread", async () => {
|
|
70
|
-
|
|
70
|
+
h.thread.isEmpty = false;
|
|
71
71
|
expect(await mount({ empty: false })).not.toBeNull();
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
it("hides children when empty:false but the thread is empty", async () => {
|
|
75
|
-
|
|
75
|
+
h.thread.isEmpty = true;
|
|
76
76
|
expect(await mount({ empty: false })).toBeNull();
|
|
77
77
|
});
|
|
78
78
|
});
|
|
@@ -101,19 +101,19 @@ describe("ThreadIf", () => {
|
|
|
101
101
|
|
|
102
102
|
describe("combined guards", () => {
|
|
103
103
|
it("renders children only when both empty and running match", async () => {
|
|
104
|
-
|
|
104
|
+
h.thread.isEmpty = true;
|
|
105
105
|
h.thread.isRunning = true;
|
|
106
106
|
expect(await mount({ empty: true, running: true })).not.toBeNull();
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
it("hides children when empty matches but running does not", async () => {
|
|
110
|
-
|
|
110
|
+
h.thread.isEmpty = true;
|
|
111
111
|
h.thread.isRunning = false;
|
|
112
112
|
expect(await mount({ empty: true, running: true })).toBeNull();
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
it("hides children when running matches but empty does not", async () => {
|
|
116
|
-
|
|
116
|
+
h.thread.isEmpty = false;
|
|
117
117
|
h.thread.isRunning = true;
|
|
118
118
|
expect(await mount({ empty: true, running: true })).toBeNull();
|
|
119
119
|
});
|
|
@@ -7,17 +7,14 @@ export type ThreadIfProps = {
|
|
|
7
7
|
running?: boolean | undefined;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated Use `<AuiIf condition={(s) => s.thread...} />` instead.
|
|
12
|
+
*/
|
|
10
13
|
export const ThreadIf = ({ children, empty, running }: ThreadIfProps) => {
|
|
11
14
|
const thread = useAuiState((s) => s.thread);
|
|
12
15
|
|
|
13
|
-
if (empty !== undefined)
|
|
14
|
-
|
|
15
|
-
if (empty !== isEmpty) return null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (running !== undefined) {
|
|
19
|
-
if (running !== thread.isRunning) return null;
|
|
20
|
-
}
|
|
16
|
+
if (empty !== undefined && empty !== thread.isEmpty) return null;
|
|
17
|
+
if (running !== undefined && running !== thread.isRunning) return null;
|
|
21
18
|
|
|
22
19
|
return <>{children}</>;
|
|
23
20
|
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
1
|
import { Pressable, type PressableProps } from "react-native";
|
|
3
2
|
import { useSuggestionTrigger } from "@assistant-ui/core/react";
|
|
4
3
|
|
|
5
|
-
export type ThreadSuggestionProps = Omit<
|
|
6
|
-
|
|
4
|
+
export type ThreadSuggestionProps = Omit<
|
|
5
|
+
PressableProps,
|
|
6
|
+
"onPress" | "children"
|
|
7
|
+
> & {
|
|
8
|
+
children: PressableProps["children"];
|
|
7
9
|
/** The suggestion prompt. */
|
|
8
10
|
prompt: string;
|
|
9
11
|
/**
|
|
@@ -37,6 +39,7 @@ export const ThreadSuggestion = ({
|
|
|
37
39
|
<Pressable
|
|
38
40
|
onPress={trigger}
|
|
39
41
|
disabled={disabledProp ?? disabled}
|
|
42
|
+
accessibilityRole="button"
|
|
40
43
|
{...pressableProps}
|
|
41
44
|
>
|
|
42
45
|
{children}
|
|
@@ -5,11 +5,44 @@ import { ThreadListNew } from "./ThreadListNew";
|
|
|
5
5
|
|
|
6
6
|
const h = vi.hoisted(() => ({
|
|
7
7
|
switchToNewThread: vi.fn<() => void>(),
|
|
8
|
+
state: { threads: { newThreadId: "new", mainThreadId: "other" } },
|
|
9
|
+
pressableProps: null as Record<string, unknown> | null,
|
|
8
10
|
}));
|
|
9
11
|
|
|
10
|
-
vi.mock("@assistant-ui/core/react", () =>
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
vi.mock("@assistant-ui/core/react", async (importOriginal) => {
|
|
13
|
+
const actual =
|
|
14
|
+
await importOriginal<typeof import("@assistant-ui/core/react")>();
|
|
15
|
+
return {
|
|
16
|
+
...actual,
|
|
17
|
+
useThreadListNew: () => ({ switchToNewThread: h.switchToNewThread }),
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => {
|
|
22
|
+
const actual = await importOriginal<typeof import("@assistant-ui/store")>();
|
|
23
|
+
return {
|
|
24
|
+
...actual,
|
|
25
|
+
useAuiState: <T,>(selector: (s: typeof h.state) => T) => selector(h.state),
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
vi.mock("react-native", async (importOriginal) => {
|
|
30
|
+
const actual = await importOriginal<typeof import("react-native")>();
|
|
31
|
+
const React = await import("react");
|
|
32
|
+
|
|
33
|
+
const PressableMock = React.forwardRef(function PressableMock(
|
|
34
|
+
props: Record<string, unknown>,
|
|
35
|
+
ref: React.Ref<unknown>,
|
|
36
|
+
) {
|
|
37
|
+
h.pressableProps = props;
|
|
38
|
+
return React.createElement(
|
|
39
|
+
actual.Pressable as unknown as React.ElementType,
|
|
40
|
+
{ ...props, ref },
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return { ...actual, Pressable: PressableMock };
|
|
45
|
+
});
|
|
13
46
|
|
|
14
47
|
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
15
48
|
|
|
@@ -24,6 +57,8 @@ describe("ThreadListNew", () => {
|
|
|
24
57
|
|
|
25
58
|
beforeEach(() => {
|
|
26
59
|
h.switchToNewThread.mockReset();
|
|
60
|
+
h.state.threads = { newThreadId: "new", mainThreadId: "other" };
|
|
61
|
+
h.pressableProps = null;
|
|
27
62
|
|
|
28
63
|
container = document.createElement("div");
|
|
29
64
|
document.body.appendChild(container);
|
|
@@ -50,6 +85,11 @@ describe("ThreadListNew", () => {
|
|
|
50
85
|
return container.querySelector('[data-testid="t"]') as HTMLElement;
|
|
51
86
|
};
|
|
52
87
|
|
|
88
|
+
const accessibilityState = () =>
|
|
89
|
+
h.pressableProps?.["accessibilityState"] as
|
|
90
|
+
| Record<string, unknown>
|
|
91
|
+
| undefined;
|
|
92
|
+
|
|
53
93
|
it("fires switchToNewThread when pressed", async () => {
|
|
54
94
|
const el = await mount();
|
|
55
95
|
|
|
@@ -69,4 +109,79 @@ describe("ThreadListNew", () => {
|
|
|
69
109
|
|
|
70
110
|
expect(h.switchToNewThread).not.toHaveBeenCalled();
|
|
71
111
|
});
|
|
112
|
+
|
|
113
|
+
it("marks itself selected while the new thread is the current one", async () => {
|
|
114
|
+
h.state.threads = { newThreadId: "new", mainThreadId: "new" };
|
|
115
|
+
|
|
116
|
+
await mount();
|
|
117
|
+
|
|
118
|
+
expect(accessibilityState()).toMatchObject({ selected: true });
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("is not selected while another thread is current", async () => {
|
|
122
|
+
await mount();
|
|
123
|
+
|
|
124
|
+
expect(accessibilityState()).toMatchObject({ selected: false });
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("keeps other accessibility state the caller passes", async () => {
|
|
128
|
+
h.state.threads = { newThreadId: "new", mainThreadId: "new" };
|
|
129
|
+
|
|
130
|
+
await mount({ accessibilityState: { busy: true } });
|
|
131
|
+
|
|
132
|
+
expect(accessibilityState()).toMatchObject({ selected: true, busy: true });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("lets the caller override the selected state", async () => {
|
|
136
|
+
h.state.threads = { newThreadId: "new", mainThreadId: "new" };
|
|
137
|
+
|
|
138
|
+
await mount({ accessibilityState: { selected: false } });
|
|
139
|
+
|
|
140
|
+
expect(accessibilityState()).toMatchObject({ selected: false });
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("lets the caller select a control the state reports inactive", async () => {
|
|
144
|
+
await mount({ accessibilityState: { selected: true } });
|
|
145
|
+
|
|
146
|
+
expect(accessibilityState()).toMatchObject({ selected: true });
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("passes isActive alongside press state to render children", async () => {
|
|
150
|
+
h.state.threads = { newThreadId: "new", mainThreadId: "new" };
|
|
151
|
+
let renderState: unknown = null;
|
|
152
|
+
|
|
153
|
+
await act(async () => {
|
|
154
|
+
root.render(
|
|
155
|
+
<ThreadListNew testID="t">
|
|
156
|
+
{(state) => {
|
|
157
|
+
renderState = state;
|
|
158
|
+
return state.isActive ? "current" : "not current";
|
|
159
|
+
}}
|
|
160
|
+
</ThreadListNew>,
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const el = container.querySelector('[data-testid="t"]') as HTMLElement;
|
|
165
|
+
expect(el.textContent).toBe("current");
|
|
166
|
+
expect(renderState).toMatchObject({ pressed: false, isActive: true });
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("reports an inactive render state while another thread is current", async () => {
|
|
170
|
+
let renderState: unknown = null;
|
|
171
|
+
|
|
172
|
+
await act(async () => {
|
|
173
|
+
root.render(
|
|
174
|
+
<ThreadListNew testID="t">
|
|
175
|
+
{(state) => {
|
|
176
|
+
renderState = state;
|
|
177
|
+
return state.isActive ? "current" : "not current";
|
|
178
|
+
}}
|
|
179
|
+
</ThreadListNew>,
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const el = container.querySelector('[data-testid="t"]') as HTMLElement;
|
|
184
|
+
expect(el.textContent).toBe("not current");
|
|
185
|
+
expect(renderState).toMatchObject({ pressed: false, isActive: false });
|
|
186
|
+
});
|
|
72
187
|
});
|
|
@@ -1,20 +1,43 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Pressable,
|
|
4
|
+
type PressableProps,
|
|
5
|
+
type PressableStateCallbackType,
|
|
6
|
+
} from "react-native";
|
|
7
|
+
import { useAuiState } from "@assistant-ui/store";
|
|
3
8
|
import { useThreadListNew } from "@assistant-ui/core/react";
|
|
4
9
|
|
|
5
|
-
export type ThreadListNewProps = Omit<
|
|
6
|
-
|
|
10
|
+
export type ThreadListNewProps = Omit<
|
|
11
|
+
PressableProps,
|
|
12
|
+
"onPress" | "children"
|
|
13
|
+
> & {
|
|
14
|
+
children:
|
|
15
|
+
| ReactNode
|
|
16
|
+
| ((
|
|
17
|
+
state: PressableStateCallbackType & { isActive: boolean },
|
|
18
|
+
) => ReactNode);
|
|
7
19
|
};
|
|
8
20
|
|
|
9
21
|
export const ThreadListNew = ({
|
|
10
22
|
children,
|
|
23
|
+
accessibilityState,
|
|
11
24
|
...pressableProps
|
|
12
25
|
}: ThreadListNewProps) => {
|
|
26
|
+
const isActive = useAuiState(
|
|
27
|
+
(s) => s.threads.newThreadId === s.threads.mainThreadId,
|
|
28
|
+
);
|
|
13
29
|
const { switchToNewThread } = useThreadListNew();
|
|
14
30
|
|
|
15
31
|
return (
|
|
16
|
-
<Pressable
|
|
17
|
-
{
|
|
32
|
+
<Pressable
|
|
33
|
+
onPress={switchToNewThread}
|
|
34
|
+
accessibilityRole="button"
|
|
35
|
+
accessibilityState={{ selected: isActive, ...accessibilityState }}
|
|
36
|
+
{...pressableProps}
|
|
37
|
+
>
|
|
38
|
+
{typeof children === "function"
|
|
39
|
+
? (state) => children({ ...state, isActive })
|
|
40
|
+
: children}
|
|
18
41
|
</Pressable>
|
|
19
42
|
);
|
|
20
43
|
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
1
|
import { Pressable, type PressableProps } from "react-native";
|
|
3
2
|
import { useThreadListItemArchive } from "@assistant-ui/core/react";
|
|
4
3
|
|
|
5
|
-
export type ThreadListItemArchiveProps = Omit<
|
|
6
|
-
|
|
4
|
+
export type ThreadListItemArchiveProps = Omit<
|
|
5
|
+
PressableProps,
|
|
6
|
+
"onPress" | "children"
|
|
7
|
+
> & {
|
|
8
|
+
children: PressableProps["children"];
|
|
7
9
|
};
|
|
8
10
|
|
|
9
11
|
export const ThreadListItemArchive = ({
|
|
@@ -13,7 +15,7 @@ export const ThreadListItemArchive = ({
|
|
|
13
15
|
const { archive } = useThreadListItemArchive();
|
|
14
16
|
|
|
15
17
|
return (
|
|
16
|
-
<Pressable onPress={archive} {...pressableProps}>
|
|
18
|
+
<Pressable onPress={archive} accessibilityRole="button" {...pressableProps}>
|
|
17
19
|
{children}
|
|
18
20
|
</Pressable>
|
|
19
21
|
);
|