@assistant-ui/react-native 0.1.44 → 0.1.46
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/LICENSE +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarEdit.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarReload.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarSpeak.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -1
- package/dist/primitives/attachment/AttachmentName.js.map +1 -1
- 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/BranchPickerCount.js.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerNext.js.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerNumber.js.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerPrevious.js.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtRoot.js.map +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
- package/dist/primitives/composer/ComposerCancel.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerQuote.js.map +1 -1
- package/dist/primitives/composer/ComposerQuoteDismiss.js.map +1 -1
- package/dist/primitives/composer/ComposerQuoteText.js.map +1 -1
- package/dist/primitives/composer/ComposerRoot.js.map +1 -1
- package/dist/primitives/composer/ComposerSend.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/MessageContent.d.ts.map +1 -1
- package/dist/primitives/message/MessageContent.js +43 -33
- package/dist/primitives/message/MessageContent.js.map +1 -1
- 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/message.d.ts +1 -0
- package/dist/primitives/message.d.ts.map +1 -1
- package/dist/primitives/message.js +3 -2
- package/dist/primitives/message.js.map +1 -1
- package/dist/primitives/messagePart/MessagePartImage.js.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemText.js.map +1 -1
- package/dist/primitives/suggestion/SuggestionDescription.js.map +1 -1
- package/dist/primitives/suggestion/SuggestionTitle.js.map +1 -1
- package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
- package/dist/primitives/thread/ThreadEmpty.js.map +1 -1
- package/dist/primitives/thread/ThreadIf.js.map +1 -1
- package/dist/primitives/thread/ThreadMessages.d.ts +7 -5
- package/dist/primitives/thread/ThreadMessages.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadMessages.js +109 -376
- package/dist/primitives/thread/ThreadMessages.js.map +1 -1
- package/dist/primitives/thread/ThreadRoot.js.map +1 -1
- package/dist/primitives/thread/ThreadRowsFlatList.d.ts +29 -0
- package/dist/primitives/thread/ThreadRowsFlatList.d.ts.map +1 -0
- package/dist/primitives/thread/ThreadRowsFlatList.js +226 -0
- package/dist/primitives/thread/ThreadRowsFlatList.js.map +1 -0
- package/dist/primitives/thread/ThreadSuggestion.js.map +1 -1
- package/dist/primitives/thread/flatListScroll.d.ts +52 -0
- package/dist/primitives/thread/flatListScroll.d.ts.map +1 -0
- package/dist/primitives/thread/flatListScroll.js +380 -0
- package/dist/primitives/thread/flatListScroll.js.map +1 -0
- package/dist/primitives/thread.d.ts +2 -0
- package/dist/primitives/thread.d.ts.map +1 -1
- package/dist/primitives/thread.js +5 -2
- package/dist/primitives/thread.js.map +1 -1
- package/dist/primitives/threadList/ThreadListItems.js.map +1 -1
- package/dist/primitives/threadList/ThreadListNew.js.map +1 -1
- package/dist/primitives/threadList/ThreadListRoot.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemArchive.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemDelete.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemRoot.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemUnarchive.js.map +1 -1
- package/package.json +13 -9
- package/src/index.ts +18 -1
- package/src/primitives/message/MessageContent.test.tsx +46 -1
- package/src/primitives/message/MessageContent.tsx +3 -1
- package/src/primitives/message.test.tsx +74 -0
- package/src/primitives/message.ts +1 -0
- package/src/primitives/thread/ThreadMessages.test.tsx +28 -1
- package/src/primitives/thread/ThreadMessages.tsx +49 -343
- package/src/primitives/thread/ThreadRowsFlatList.test.tsx +253 -0
- package/src/primitives/thread/ThreadRowsFlatList.tsx +159 -0
- package/src/primitives/thread/flatListScroll.ts +467 -0
- package/src/primitives/thread.ts +5 -0
|
@@ -0,0 +1,253 @@
|
|
|
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 { AssistantState } from "@assistant-ui/store";
|
|
5
|
+
import type { MessageState } from "@assistant-ui/core/store";
|
|
6
|
+
import { groupPartByType, type ThreadRow } from "@assistant-ui/core/react";
|
|
7
|
+
import { ThreadRowsFlatList } from "./ThreadRowsFlatList";
|
|
8
|
+
|
|
9
|
+
const h = vi.hoisted(() => ({
|
|
10
|
+
state: {
|
|
11
|
+
thread: {
|
|
12
|
+
messages: [] as MessageState[],
|
|
13
|
+
hasEarlier: false,
|
|
14
|
+
isLoadingEarlier: false,
|
|
15
|
+
},
|
|
16
|
+
optional: {},
|
|
17
|
+
},
|
|
18
|
+
flatListProps: null as Record<string, unknown> | null,
|
|
19
|
+
scrollToOffset: vi.fn(),
|
|
20
|
+
loadEarlier: vi.fn(async () => {}),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
vi.mock("react-native", async (importOriginal) => {
|
|
24
|
+
const actual = await importOriginal<typeof import("react-native")>();
|
|
25
|
+
const React = await import("react");
|
|
26
|
+
const FlatListMock = React.forwardRef(function FlatListMock(
|
|
27
|
+
props: Record<string, unknown>,
|
|
28
|
+
ref,
|
|
29
|
+
) {
|
|
30
|
+
h.flatListProps = props;
|
|
31
|
+
React.useImperativeHandle(ref, () => ({
|
|
32
|
+
scrollToOffset: h.scrollToOffset,
|
|
33
|
+
}));
|
|
34
|
+
const data = props.data as ThreadRow[];
|
|
35
|
+
const renderItem = props.renderItem as (value: {
|
|
36
|
+
item: ThreadRow;
|
|
37
|
+
index: number;
|
|
38
|
+
}) => React.ReactNode;
|
|
39
|
+
const keyExtractor = props.keyExtractor as (row: ThreadRow) => string;
|
|
40
|
+
return React.createElement(
|
|
41
|
+
"div",
|
|
42
|
+
null,
|
|
43
|
+
data.map((item, index) =>
|
|
44
|
+
React.createElement(
|
|
45
|
+
"div",
|
|
46
|
+
{ key: keyExtractor(item) },
|
|
47
|
+
renderItem({ item, index }),
|
|
48
|
+
),
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
return { ...actual, FlatList: FlatListMock };
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => {
|
|
56
|
+
const actual = await importOriginal<typeof import("@assistant-ui/store")>();
|
|
57
|
+
return {
|
|
58
|
+
...actual,
|
|
59
|
+
useAui: () => ({ thread: { loadEarlier: h.loadEarlier } }),
|
|
60
|
+
useAuiState: <T,>(selector: (state: AssistantState) => T) =>
|
|
61
|
+
selector(h.state as unknown as AssistantState),
|
|
62
|
+
useAuiEvent: () => {},
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
vi.mock("@assistant-ui/core/react", async (importOriginal) => {
|
|
67
|
+
const actual =
|
|
68
|
+
await importOriginal<typeof import("@assistant-ui/core/react")>();
|
|
69
|
+
return {
|
|
70
|
+
...actual,
|
|
71
|
+
ThreadPrimitiveRow: ({
|
|
72
|
+
row,
|
|
73
|
+
children,
|
|
74
|
+
}: {
|
|
75
|
+
row: ThreadRow;
|
|
76
|
+
children: (info: {
|
|
77
|
+
type: ThreadRow["type"];
|
|
78
|
+
row: ThreadRow;
|
|
79
|
+
}) => React.ReactNode;
|
|
80
|
+
}) => children({ type: row.type, row }),
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
85
|
+
|
|
86
|
+
const message = (
|
|
87
|
+
id: string,
|
|
88
|
+
role: "user" | "assistant",
|
|
89
|
+
parts: { type: string; text: string }[] = [],
|
|
90
|
+
) =>
|
|
91
|
+
({
|
|
92
|
+
id,
|
|
93
|
+
role,
|
|
94
|
+
parts: parts.map((part) => ({ ...part, status: { type: "complete" } })),
|
|
95
|
+
content: parts,
|
|
96
|
+
status: { type: "complete", reason: "stop" },
|
|
97
|
+
createdAt: new Date(0),
|
|
98
|
+
metadata: { custom: {} },
|
|
99
|
+
composer: { isEditing: false },
|
|
100
|
+
}) as unknown as MessageState;
|
|
101
|
+
|
|
102
|
+
const getProps = () => {
|
|
103
|
+
if (!h.flatListProps) throw new Error("FlatList was not rendered");
|
|
104
|
+
return h.flatListProps;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const reachedInfo = { distanceFromEnd: 0 };
|
|
108
|
+
|
|
109
|
+
describe("ThreadRowsFlatList", () => {
|
|
110
|
+
let container: HTMLDivElement;
|
|
111
|
+
let root: Root;
|
|
112
|
+
const children = (info: { type: ThreadRow["type"]; row: ThreadRow }) => (
|
|
113
|
+
<span>{`${info.type}:${info.row.key}`}</span>
|
|
114
|
+
);
|
|
115
|
+
const mount = async (
|
|
116
|
+
props: Omit<
|
|
117
|
+
React.ComponentProps<typeof ThreadRowsFlatList>,
|
|
118
|
+
"children"
|
|
119
|
+
> = {},
|
|
120
|
+
) => {
|
|
121
|
+
await act(async () => {
|
|
122
|
+
root.render(
|
|
123
|
+
<ThreadRowsFlatList {...props}>{children}</ThreadRowsFlatList>,
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
beforeEach(() => {
|
|
129
|
+
h.state.thread.messages = [
|
|
130
|
+
message("u1", "user"),
|
|
131
|
+
message("a1", "assistant", [{ type: "text", text: "answer" }]),
|
|
132
|
+
];
|
|
133
|
+
h.state.thread.hasEarlier = false;
|
|
134
|
+
h.state.thread.isLoadingEarlier = false;
|
|
135
|
+
h.flatListProps = null;
|
|
136
|
+
h.scrollToOffset.mockReset();
|
|
137
|
+
h.loadEarlier.mockReset();
|
|
138
|
+
container = document.createElement("div");
|
|
139
|
+
document.body.appendChild(container);
|
|
140
|
+
root = createRoot(container);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
afterEach(async () => {
|
|
144
|
+
await act(async () => root.unmount());
|
|
145
|
+
container.remove();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("renders part and turn-end rows through the render function and keys rows", async () => {
|
|
149
|
+
await mount();
|
|
150
|
+
const props = getProps();
|
|
151
|
+
const data = props.data as ThreadRow[];
|
|
152
|
+
expect(data.map((row) => row.type)).toEqual([
|
|
153
|
+
"message",
|
|
154
|
+
"part",
|
|
155
|
+
"turn-end",
|
|
156
|
+
]);
|
|
157
|
+
expect(container.textContent).toContain("part:part:a1:");
|
|
158
|
+
expect(container.textContent).toContain("turn-end:turn-end:u1");
|
|
159
|
+
expect((props.keyExtractor as (row: ThreadRow) => string)(data[1]!)).toBe(
|
|
160
|
+
data[1]!.key,
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("coalesces a reasoning run with groupBy", async () => {
|
|
165
|
+
h.state.thread.messages = [
|
|
166
|
+
message("a1", "assistant", [
|
|
167
|
+
{ type: "reasoning", text: "think" },
|
|
168
|
+
{ type: "reasoning", text: "more" },
|
|
169
|
+
{ type: "text", text: "answer" },
|
|
170
|
+
]),
|
|
171
|
+
];
|
|
172
|
+
await mount({
|
|
173
|
+
groupBy: groupPartByType({ reasoning: ["group-reasoning"] }),
|
|
174
|
+
});
|
|
175
|
+
const data = getProps().data as ThreadRow[];
|
|
176
|
+
expect(data[0]).toMatchObject({
|
|
177
|
+
type: "part",
|
|
178
|
+
group: "group-reasoning",
|
|
179
|
+
indices: [0, 1],
|
|
180
|
+
});
|
|
181
|
+
expect(data.map((row) => row.type)).toEqual(["part", "part", "turn-end"]);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("anchors normal rows and pages from the start", async () => {
|
|
185
|
+
h.state.thread.hasEarlier = true;
|
|
186
|
+
const onStartReached = vi.fn();
|
|
187
|
+
await mount({ onStartReached });
|
|
188
|
+
const props = getProps();
|
|
189
|
+
expect(props.maintainVisibleContentPosition).toEqual({
|
|
190
|
+
minIndexForVisible: 0,
|
|
191
|
+
});
|
|
192
|
+
expect(props.onStartReachedThreshold).toBe(1);
|
|
193
|
+
(props.onStartReached as (info: typeof reachedInfo) => void)(reachedInfo);
|
|
194
|
+
expect(onStartReached).toHaveBeenCalledWith(reachedInfo);
|
|
195
|
+
expect(h.loadEarlier).toHaveBeenCalledOnce();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("reverses rows, pages from the end, and initializes at offset zero", async () => {
|
|
199
|
+
h.state.thread.hasEarlier = true;
|
|
200
|
+
const onStartReached = vi.fn();
|
|
201
|
+
const onEndReached = vi.fn();
|
|
202
|
+
await mount({ inverted: true, onStartReached, onEndReached });
|
|
203
|
+
const props = getProps();
|
|
204
|
+
expect((props.data as ThreadRow[]).map((row) => row.type)).toEqual([
|
|
205
|
+
"turn-end",
|
|
206
|
+
"part",
|
|
207
|
+
"message",
|
|
208
|
+
]);
|
|
209
|
+
expect(props.maintainVisibleContentPosition).toEqual({
|
|
210
|
+
minIndexForVisible: 0,
|
|
211
|
+
autoscrollToTopThreshold: 4,
|
|
212
|
+
});
|
|
213
|
+
expect(props.onEndReachedThreshold).toBe(1);
|
|
214
|
+
expect(props.onStartReached).toBe(onStartReached);
|
|
215
|
+
expect(h.scrollToOffset).toHaveBeenCalledWith({
|
|
216
|
+
offset: 0,
|
|
217
|
+
animated: false,
|
|
218
|
+
});
|
|
219
|
+
(props.onStartReached as (info: typeof reachedInfo) => void)(reachedInfo);
|
|
220
|
+
expect(h.loadEarlier).not.toHaveBeenCalled();
|
|
221
|
+
(props.onEndReached as (info: typeof reachedInfo) => void)(reachedInfo);
|
|
222
|
+
expect(onEndReached).toHaveBeenCalledWith(reachedInfo);
|
|
223
|
+
expect(h.loadEarlier).toHaveBeenCalledOnce();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("unpins an inverted list when the offset moves away from zero", async () => {
|
|
227
|
+
await mount({ inverted: true, scrollToBottomOnInitialize: false });
|
|
228
|
+
const props = getProps();
|
|
229
|
+
const onScroll = props.onScroll as (event: unknown) => void;
|
|
230
|
+
const onContentSizeChange = props.onContentSizeChange as (
|
|
231
|
+
width: number,
|
|
232
|
+
height: number,
|
|
233
|
+
) => void;
|
|
234
|
+
const scrollEvent = (offset: number) => ({
|
|
235
|
+
nativeEvent: {
|
|
236
|
+
contentOffset: { x: 0, y: offset },
|
|
237
|
+
contentSize: { width: 100, height: 400 },
|
|
238
|
+
layoutMeasurement: { width: 100, height: 100 },
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
onScroll(scrollEvent(8));
|
|
243
|
+
onContentSizeChange(100, 500);
|
|
244
|
+
expect(h.scrollToOffset).not.toHaveBeenCalled();
|
|
245
|
+
|
|
246
|
+
onScroll(scrollEvent(2));
|
|
247
|
+
onContentSizeChange(100, 600);
|
|
248
|
+
expect(h.scrollToOffset).toHaveBeenCalledWith({
|
|
249
|
+
offset: 0,
|
|
250
|
+
animated: false,
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { forwardRef, useCallback, useMemo } from "react";
|
|
2
|
+
import { FlatList, type FlatListProps } from "react-native";
|
|
3
|
+
import {
|
|
4
|
+
createThreadRowsSelector,
|
|
5
|
+
ThreadPrimitiveRow,
|
|
6
|
+
type ThreadRow,
|
|
7
|
+
type ThreadRowsOptions,
|
|
8
|
+
} from "@assistant-ui/core/react";
|
|
9
|
+
import { useAuiState } from "@assistant-ui/store";
|
|
10
|
+
import {
|
|
11
|
+
type FlatListHistory,
|
|
12
|
+
getFlatListPagingProps,
|
|
13
|
+
useComposedFlatListRef,
|
|
14
|
+
useFlatListAutoScroll,
|
|
15
|
+
useFlatListScrollProps,
|
|
16
|
+
useHistoryLoad,
|
|
17
|
+
useThreadHistory,
|
|
18
|
+
} from "./flatListScroll";
|
|
19
|
+
|
|
20
|
+
export type ThreadRowsFlatListProps = Omit<
|
|
21
|
+
FlatListProps<ThreadRow>,
|
|
22
|
+
"data" | "renderItem" | "children" | "keyExtractor"
|
|
23
|
+
> & {
|
|
24
|
+
/** Same contract as `MessagePrimitive.GroupedParts`; keep a stable identity (module scope or `useCallback`) or every row cache rebuilds and re-renders. */
|
|
25
|
+
groupBy?: ThreadRowsOptions["groupBy"];
|
|
26
|
+
/** Renders each row through `ThreadPrimitive.Row`. */
|
|
27
|
+
children: ThreadPrimitiveRow.Props["children"];
|
|
28
|
+
autoScroll?: boolean | undefined;
|
|
29
|
+
scrollToBottomOnRunStart?: boolean | undefined;
|
|
30
|
+
scrollToBottomOnInitialize?: boolean | undefined;
|
|
31
|
+
scrollToBottomOnThreadSwitch?: boolean | undefined;
|
|
32
|
+
/** Pages older messages from app state or the runtime when the list nears its history edge. */
|
|
33
|
+
history?: FlatListHistory | undefined;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Virtualizes flattened thread rows while keeping the visible row anchored. */
|
|
37
|
+
export const ThreadRowsFlatList = forwardRef<
|
|
38
|
+
FlatList<ThreadRow>,
|
|
39
|
+
ThreadRowsFlatListProps
|
|
40
|
+
>(
|
|
41
|
+
(
|
|
42
|
+
{
|
|
43
|
+
groupBy,
|
|
44
|
+
children,
|
|
45
|
+
inverted = false,
|
|
46
|
+
autoScroll,
|
|
47
|
+
scrollToBottomOnInitialize,
|
|
48
|
+
scrollToBottomOnRunStart,
|
|
49
|
+
scrollToBottomOnThreadSwitch,
|
|
50
|
+
history,
|
|
51
|
+
onContentSizeChange,
|
|
52
|
+
onLayout,
|
|
53
|
+
onScroll,
|
|
54
|
+
onStartReached,
|
|
55
|
+
onStartReachedThreshold,
|
|
56
|
+
onEndReached,
|
|
57
|
+
onEndReachedThreshold,
|
|
58
|
+
scrollEventThrottle,
|
|
59
|
+
...flatListProps
|
|
60
|
+
},
|
|
61
|
+
forwardedRef,
|
|
62
|
+
) => {
|
|
63
|
+
const selectRows = useMemo(
|
|
64
|
+
() => createThreadRowsSelector({ groupBy }),
|
|
65
|
+
[groupBy],
|
|
66
|
+
);
|
|
67
|
+
const rows = useAuiState(selectRows);
|
|
68
|
+
const data = useMemo(
|
|
69
|
+
() => (inverted ? [...rows].reverse() : rows) as ThreadRow[],
|
|
70
|
+
[inverted, rows],
|
|
71
|
+
);
|
|
72
|
+
const effectiveHistory = useThreadHistory(history);
|
|
73
|
+
const [flatListRef, setFlatListRef] = useComposedFlatListRef(forwardedRef);
|
|
74
|
+
const {
|
|
75
|
+
handleContentSizeChange: handleAutoScrollContentSizeChange,
|
|
76
|
+
handleLayout: handleAutoScrollLayout,
|
|
77
|
+
handleScroll: handleAutoScrollScroll,
|
|
78
|
+
} = useFlatListAutoScroll({
|
|
79
|
+
flatListRef,
|
|
80
|
+
hasMessages: rows.length > 0,
|
|
81
|
+
inverted,
|
|
82
|
+
horizontal: flatListProps.horizontal,
|
|
83
|
+
autoScroll,
|
|
84
|
+
scrollToBottomOnInitialize,
|
|
85
|
+
scrollToBottomOnRunStart,
|
|
86
|
+
scrollToBottomOnThreadSwitch,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const renderItem = useCallback(
|
|
90
|
+
({ item }: { item: ThreadRow }) => (
|
|
91
|
+
<ThreadPrimitiveRow row={item}>{children}</ThreadPrimitiveRow>
|
|
92
|
+
),
|
|
93
|
+
[children],
|
|
94
|
+
);
|
|
95
|
+
const keyExtractor = useCallback((row: ThreadRow) => row.key, []);
|
|
96
|
+
|
|
97
|
+
const scrollProps = useFlatListScrollProps({
|
|
98
|
+
autoScroll,
|
|
99
|
+
scrollToBottomOnInitialize,
|
|
100
|
+
scrollToBottomOnRunStart,
|
|
101
|
+
scrollToBottomOnThreadSwitch,
|
|
102
|
+
onContentSizeChange,
|
|
103
|
+
onLayout,
|
|
104
|
+
onScroll,
|
|
105
|
+
scrollEventThrottle,
|
|
106
|
+
handleAutoScrollContentSizeChange,
|
|
107
|
+
handleAutoScrollLayout,
|
|
108
|
+
handleAutoScrollScroll,
|
|
109
|
+
});
|
|
110
|
+
type StartInfo = Parameters<
|
|
111
|
+
NonNullable<FlatListProps<ThreadRow>["onStartReached"]>
|
|
112
|
+
>[0];
|
|
113
|
+
type EndInfo = Parameters<
|
|
114
|
+
NonNullable<FlatListProps<ThreadRow>["onEndReached"]>
|
|
115
|
+
>[0];
|
|
116
|
+
const onHistoryReached = useCallback(
|
|
117
|
+
(info: StartInfo | EndInfo) => {
|
|
118
|
+
if (inverted) onEndReached?.(info as EndInfo);
|
|
119
|
+
else onStartReached?.(info as StartInfo);
|
|
120
|
+
},
|
|
121
|
+
[inverted, onEndReached, onStartReached],
|
|
122
|
+
);
|
|
123
|
+
const { canLoadMore, handleReached } = useHistoryLoad<StartInfo | EndInfo>(
|
|
124
|
+
effectiveHistory,
|
|
125
|
+
onHistoryReached,
|
|
126
|
+
);
|
|
127
|
+
const pagingProps = getFlatListPagingProps(
|
|
128
|
+
inverted ? "end" : "start",
|
|
129
|
+
effectiveHistory,
|
|
130
|
+
canLoadMore,
|
|
131
|
+
handleReached,
|
|
132
|
+
{
|
|
133
|
+
onStartReached,
|
|
134
|
+
onStartReachedThreshold,
|
|
135
|
+
onEndReached,
|
|
136
|
+
onEndReachedThreshold,
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<FlatList
|
|
142
|
+
ref={setFlatListRef}
|
|
143
|
+
data={data}
|
|
144
|
+
renderItem={renderItem}
|
|
145
|
+
keyExtractor={keyExtractor}
|
|
146
|
+
inverted={inverted}
|
|
147
|
+
maintainVisibleContentPosition={
|
|
148
|
+
inverted
|
|
149
|
+
? { minIndexForVisible: 0, autoscrollToTopThreshold: 4 }
|
|
150
|
+
: { minIndexForVisible: 0 }
|
|
151
|
+
}
|
|
152
|
+
{...scrollProps}
|
|
153
|
+
{...pagingProps}
|
|
154
|
+
{...flatListProps}
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
ThreadRowsFlatList.displayName = "ThreadPrimitive.RowsFlatList";
|