@assistant-ui/react-native 0.1.37 → 0.1.38
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 +3 -2
- package/dist/index.js +2 -1
- 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/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/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/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/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/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/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/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/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/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/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/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 +5 -5
- package/src/index.ts +2 -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/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,163 @@
|
|
|
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 { ComposerQuote } from "./ComposerQuote";
|
|
6
|
+
import { ComposerQuoteDismiss } from "./ComposerQuoteDismiss";
|
|
7
|
+
import { ComposerQuoteText } from "./ComposerQuoteText";
|
|
8
|
+
|
|
9
|
+
const h = vi.hoisted(() => ({
|
|
10
|
+
quote: undefined as { text: string } | undefined,
|
|
11
|
+
setQuote: vi.fn<(quote: undefined) => void>(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => {
|
|
15
|
+
const actual = await importOriginal<typeof import("@assistant-ui/store")>();
|
|
16
|
+
return {
|
|
17
|
+
...actual,
|
|
18
|
+
useAui: () => ({ composer: { setQuote: h.setQuote } }),
|
|
19
|
+
useAuiState: <T,>(selector: (state: { composer: typeof h }) => T) =>
|
|
20
|
+
selector({ composer: h }),
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
25
|
+
|
|
26
|
+
describe("Composer quote primitives", () => {
|
|
27
|
+
let container: HTMLDivElement;
|
|
28
|
+
let root: Root;
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
h.quote = undefined;
|
|
32
|
+
h.setQuote.mockReset();
|
|
33
|
+
container = document.createElement("div");
|
|
34
|
+
document.body.appendChild(container);
|
|
35
|
+
root = createRoot(container);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(async () => {
|
|
39
|
+
await act(async () => {
|
|
40
|
+
root.unmount();
|
|
41
|
+
});
|
|
42
|
+
container.remove();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const render = async (element: ReactElement) => {
|
|
46
|
+
await act(async () => {
|
|
47
|
+
root.render(element);
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const press = async (testID: string) => {
|
|
52
|
+
const element = container.querySelector(`[data-testid="${testID}"]`);
|
|
53
|
+
expect(element).not.toBeNull();
|
|
54
|
+
await act(async () => {
|
|
55
|
+
element!.dispatchEvent(
|
|
56
|
+
new MouseEvent("click", { bubbles: true, cancelable: true }),
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
it("renders quote children only when a quote exists", async () => {
|
|
62
|
+
await render(
|
|
63
|
+
<ComposerQuote>
|
|
64
|
+
<Text testID="quote">quoted</Text>
|
|
65
|
+
</ComposerQuote>,
|
|
66
|
+
);
|
|
67
|
+
expect(container.querySelector('[data-testid="quote"]')).toBeNull();
|
|
68
|
+
|
|
69
|
+
h.quote = { text: "quoted text" };
|
|
70
|
+
await render(
|
|
71
|
+
<ComposerQuote>
|
|
72
|
+
<Text testID="quote">quoted</Text>
|
|
73
|
+
</ComposerQuote>,
|
|
74
|
+
);
|
|
75
|
+
expect(container.querySelector('[data-testid="quote"]')).not.toBeNull();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("renders quote text and allows children to override it", async () => {
|
|
79
|
+
h.quote = { text: "quoted text" };
|
|
80
|
+
await render(<ComposerQuoteText testID="text" />);
|
|
81
|
+
expect(container.querySelector('[data-testid="text"]')?.textContent).toBe(
|
|
82
|
+
"quoted text",
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
await render(
|
|
86
|
+
<ComposerQuoteText testID="text">
|
|
87
|
+
<Text>override</Text>
|
|
88
|
+
</ComposerQuoteText>,
|
|
89
|
+
);
|
|
90
|
+
expect(container.querySelector('[data-testid="text"]')?.textContent).toBe(
|
|
91
|
+
"override",
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("renders nothing when there is no quote text, even with children", async () => {
|
|
96
|
+
await render(
|
|
97
|
+
<ComposerQuoteText testID="text">
|
|
98
|
+
<Text>fallback</Text>
|
|
99
|
+
</ComposerQuoteText>,
|
|
100
|
+
);
|
|
101
|
+
expect(container.querySelector('[data-testid="text"]')).toBeNull();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("renders children override when quote text is an empty string", async () => {
|
|
105
|
+
h.quote = { text: "" };
|
|
106
|
+
await render(
|
|
107
|
+
<ComposerQuoteText testID="text">
|
|
108
|
+
<Text>override</Text>
|
|
109
|
+
</ComposerQuoteText>,
|
|
110
|
+
);
|
|
111
|
+
expect(container.querySelector('[data-testid="text"]')?.textContent).toBe(
|
|
112
|
+
"override",
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("renders an empty Text when quote text is an empty string and no children are provided", async () => {
|
|
117
|
+
h.quote = { text: "" };
|
|
118
|
+
await render(<ComposerQuoteText testID="text" />);
|
|
119
|
+
expect(container.querySelector('[data-testid="text"]')?.textContent).toBe(
|
|
120
|
+
"",
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("clears the quote when dismissed", async () => {
|
|
125
|
+
await render(
|
|
126
|
+
<ComposerQuoteDismiss testID="dismiss">
|
|
127
|
+
<Text>dismiss</Text>
|
|
128
|
+
</ComposerQuoteDismiss>,
|
|
129
|
+
);
|
|
130
|
+
await press("dismiss");
|
|
131
|
+
expect(h.setQuote).toHaveBeenCalledWith(undefined);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("does not clear the quote when dismiss is disabled", async () => {
|
|
135
|
+
await render(
|
|
136
|
+
<ComposerQuoteDismiss testID="dismiss" disabled>
|
|
137
|
+
<Text>dismiss</Text>
|
|
138
|
+
</ComposerQuoteDismiss>,
|
|
139
|
+
);
|
|
140
|
+
await press("dismiss");
|
|
141
|
+
expect(h.setQuote).not.toHaveBeenCalled();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("passes the Pressable press state to function children", async () => {
|
|
145
|
+
await render(
|
|
146
|
+
<ComposerQuoteDismiss testID="dismiss">
|
|
147
|
+
{({ pressed }) => (pressed ? "pressed" : "idle")}
|
|
148
|
+
</ComposerQuoteDismiss>,
|
|
149
|
+
);
|
|
150
|
+
expect(
|
|
151
|
+
container.querySelector('[data-testid="dismiss"]')?.textContent,
|
|
152
|
+
).toBe("idle");
|
|
153
|
+
|
|
154
|
+
await render(
|
|
155
|
+
<ComposerQuoteDismiss testID="dismiss" testOnly_pressed>
|
|
156
|
+
{({ pressed }) => (pressed ? "pressed" : "idle")}
|
|
157
|
+
</ComposerQuoteDismiss>,
|
|
158
|
+
);
|
|
159
|
+
expect(
|
|
160
|
+
container.querySelector('[data-testid="dismiss"]')?.textContent,
|
|
161
|
+
).toBe("pressed");
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { View, type ViewProps } from "react-native";
|
|
3
|
+
import { useAuiState } from "@assistant-ui/store";
|
|
4
|
+
|
|
5
|
+
export type ComposerQuoteProps = ViewProps & {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const ComposerQuote = ({
|
|
10
|
+
children,
|
|
11
|
+
...viewProps
|
|
12
|
+
}: ComposerQuoteProps) => {
|
|
13
|
+
const hasQuote = useAuiState((s) => s.composer.quote !== undefined);
|
|
14
|
+
if (!hasQuote) return null;
|
|
15
|
+
|
|
16
|
+
return <View {...viewProps}>{children}</View>;
|
|
17
|
+
};
|
|
@@ -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 ComposerQuoteDismissProps = Omit<
|
|
6
|
+
PressableProps,
|
|
7
|
+
"onPress" | "children"
|
|
8
|
+
> & {
|
|
9
|
+
children: PressableProps["children"];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const ComposerQuoteDismiss = ({
|
|
13
|
+
children,
|
|
14
|
+
...pressableProps
|
|
15
|
+
}: ComposerQuoteDismissProps) => {
|
|
16
|
+
const aui = useAui();
|
|
17
|
+
|
|
18
|
+
const handleDismiss = useCallback(() => {
|
|
19
|
+
aui.composer.setQuote(undefined);
|
|
20
|
+
}, [aui]);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Pressable
|
|
24
|
+
onPress={handleDismiss}
|
|
25
|
+
accessibilityRole="button"
|
|
26
|
+
{...pressableProps}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</Pressable>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
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 ComposerQuoteTextProps = TextProps & {
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const ComposerQuoteText = ({
|
|
10
|
+
children,
|
|
11
|
+
...textProps
|
|
12
|
+
}: ComposerQuoteTextProps) => {
|
|
13
|
+
const text = useAuiState((s) => s.composer.quote?.text);
|
|
14
|
+
if (text === undefined) return null;
|
|
15
|
+
|
|
16
|
+
return <Text {...textProps}>{children ?? text}</Text>;
|
|
17
|
+
};
|
|
@@ -113,4 +113,30 @@ describe("ComposerSend", () => {
|
|
|
113
113
|
|
|
114
114
|
expect(h.send).toHaveBeenCalledTimes(1);
|
|
115
115
|
});
|
|
116
|
+
|
|
117
|
+
it("passes the Pressable press state to function children", async () => {
|
|
118
|
+
await act(async () => {
|
|
119
|
+
root.render(
|
|
120
|
+
<ComposerSend testID="send">
|
|
121
|
+
{({ pressed }) => (pressed ? "pressed" : "idle")}
|
|
122
|
+
</ComposerSend>,
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(container.querySelector('[data-testid="send"]')?.textContent).toBe(
|
|
127
|
+
"idle",
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
await act(async () => {
|
|
131
|
+
root.render(
|
|
132
|
+
<ComposerSend testID="send" testOnly_pressed>
|
|
133
|
+
{({ pressed }) => (pressed ? "pressed" : "idle")}
|
|
134
|
+
</ComposerSend>,
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(container.querySelector('[data-testid="send"]')?.textContent).toBe(
|
|
139
|
+
"pressed",
|
|
140
|
+
);
|
|
141
|
+
});
|
|
116
142
|
});
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
1
|
import { Pressable, type PressableProps } from "react-native";
|
|
3
2
|
import { useComposerSend } from "@assistant-ui/core/react";
|
|
4
3
|
|
|
5
|
-
export type ComposerSendProps = Omit<PressableProps, "onPress"> & {
|
|
6
|
-
children:
|
|
4
|
+
export type ComposerSendProps = Omit<PressableProps, "onPress" | "children"> & {
|
|
5
|
+
children: PressableProps["children"];
|
|
7
6
|
};
|
|
8
7
|
|
|
9
8
|
export const ComposerSend = ({
|
|
@@ -17,6 +16,7 @@ export const ComposerSend = ({
|
|
|
17
16
|
<Pressable
|
|
18
17
|
onPress={() => send()}
|
|
19
18
|
disabled={disabled ?? hookDisabled}
|
|
19
|
+
accessibilityRole="button"
|
|
20
20
|
{...pressableProps}
|
|
21
21
|
>
|
|
22
22
|
{children}
|
|
@@ -25,3 +25,16 @@ export {
|
|
|
25
25
|
type ComposerAddAttachmentProps as AddAttachmentProps,
|
|
26
26
|
} from "./composer/ComposerAddAttachment";
|
|
27
27
|
export { ComposerIf as If } from "./composer/ComposerIf";
|
|
28
|
+
export {
|
|
29
|
+
ComposerQuote as Quote,
|
|
30
|
+
type ComposerQuoteProps as QuoteProps,
|
|
31
|
+
} from "./composer/ComposerQuote";
|
|
32
|
+
export {
|
|
33
|
+
ComposerQuoteText as QuoteText,
|
|
34
|
+
type ComposerQuoteTextProps as QuoteTextProps,
|
|
35
|
+
} from "./composer/ComposerQuoteText";
|
|
36
|
+
export {
|
|
37
|
+
ComposerQuoteDismiss as QuoteDismiss,
|
|
38
|
+
type ComposerQuoteDismissProps as QuoteDismissProps,
|
|
39
|
+
} from "./composer/ComposerQuoteDismiss";
|
|
40
|
+
export { ComposerQueue as Queue } from "./composer/ComposerQueue";
|
|
@@ -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
|
+
);
|