@assistant-ui/react-native 0.1.45 → 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/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- 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/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/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/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/package.json +4 -3
- package/src/index.ts +18 -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
|
@@ -10,13 +10,18 @@ type Msg = { id: string; role: string };
|
|
|
10
10
|
|
|
11
11
|
const h = vi.hoisted(() => ({
|
|
12
12
|
state: {
|
|
13
|
-
thread: {
|
|
13
|
+
thread: {
|
|
14
|
+
messages: [] as Msg[],
|
|
15
|
+
hasEarlier: false,
|
|
16
|
+
isLoadingEarlier: false,
|
|
17
|
+
},
|
|
14
18
|
message: { role: "user" as string, composer: { isEditing: false } },
|
|
15
19
|
},
|
|
16
20
|
itemState: { role: "user" } as { role: string; parts?: unknown[] },
|
|
17
21
|
events: {} as Record<string, Set<() => void>>,
|
|
18
22
|
flatListProps: null as Record<string, unknown> | null,
|
|
19
23
|
scrollToOffset: vi.fn(),
|
|
24
|
+
loadEarlier: vi.fn(async () => {}),
|
|
20
25
|
}));
|
|
21
26
|
|
|
22
27
|
vi.mock("react-native", async (importOriginal) => {
|
|
@@ -79,7 +84,9 @@ vi.mock("react-native", async (importOriginal) => {
|
|
|
79
84
|
vi.mock("@assistant-ui/store", async () => {
|
|
80
85
|
const React = await import("react");
|
|
81
86
|
|
|
87
|
+
const aui = { thread: { loadEarlier: h.loadEarlier } };
|
|
82
88
|
return {
|
|
89
|
+
useAui: () => aui,
|
|
83
90
|
useAuiState: <T,>(selector: (s: typeof h.state) => T) => selector(h.state),
|
|
84
91
|
useAuiEvent: (
|
|
85
92
|
selector: string | { scope: string; event: string },
|
|
@@ -400,6 +407,26 @@ describe("ThreadMessages", () => {
|
|
|
400
407
|
});
|
|
401
408
|
|
|
402
409
|
describe("MessagesFlatList history", () => {
|
|
410
|
+
it("pages through the runtime when no history prop is given", async () => {
|
|
411
|
+
h.state.thread.hasEarlier = true;
|
|
412
|
+
try {
|
|
413
|
+
await mountFlatList({ components: messageComponents });
|
|
414
|
+
getFlatListProps().onStartReached?.({ distanceFromStart: 0 });
|
|
415
|
+
expect(h.loadEarlier).toHaveBeenCalledOnce();
|
|
416
|
+
|
|
417
|
+
h.state.thread.isLoadingEarlier = true;
|
|
418
|
+
await mountFlatList({ components: messageComponents });
|
|
419
|
+
expect(getFlatListProps().onStartReached).toBeUndefined();
|
|
420
|
+
} finally {
|
|
421
|
+
h.state.thread.hasEarlier = false;
|
|
422
|
+
h.state.thread.isLoadingEarlier = false;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
await mountFlatList({ components: messageComponents });
|
|
426
|
+
expect(getFlatListProps().onStartReached).toBeUndefined();
|
|
427
|
+
expect(getFlatListProps().onStartReachedThreshold).toBeUndefined();
|
|
428
|
+
});
|
|
429
|
+
|
|
403
430
|
it("wires loadMore only while history can load more", async () => {
|
|
404
431
|
const loadMore = vi.fn();
|
|
405
432
|
|
|
@@ -1,30 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type ComponentType,
|
|
3
3
|
type FC,
|
|
4
|
-
type ForwardedRef,
|
|
5
4
|
type ReactNode,
|
|
6
|
-
type RefObject,
|
|
7
5
|
forwardRef,
|
|
8
6
|
memo,
|
|
9
7
|
useCallback,
|
|
10
|
-
useEffect,
|
|
11
|
-
useRef,
|
|
12
8
|
} from "react";
|
|
13
|
-
import {
|
|
14
|
-
FlatList,
|
|
15
|
-
type FlatListProps,
|
|
16
|
-
type LayoutChangeEvent,
|
|
17
|
-
type NativeScrollEvent,
|
|
18
|
-
type NativeSyntheticEvent,
|
|
19
|
-
} from "react-native";
|
|
9
|
+
import { FlatList, type FlatListProps } from "react-native";
|
|
20
10
|
import type { ThreadMessage } from "@assistant-ui/core";
|
|
21
11
|
import type { MessageState } from "@assistant-ui/core/store";
|
|
22
|
-
import {
|
|
23
|
-
RenderChildrenWithAccessor,
|
|
24
|
-
useAuiEvent,
|
|
25
|
-
useAuiState,
|
|
26
|
-
} from "@assistant-ui/store";
|
|
12
|
+
import { RenderChildrenWithAccessor, useAuiState } from "@assistant-ui/store";
|
|
27
13
|
import { MessageByIndexProvider } from "@assistant-ui/core/react";
|
|
14
|
+
import {
|
|
15
|
+
type FlatListHistory,
|
|
16
|
+
getFlatListPagingProps,
|
|
17
|
+
useComposedFlatListRef,
|
|
18
|
+
useFlatListAutoScroll,
|
|
19
|
+
useFlatListScrollProps,
|
|
20
|
+
useHistoryLoad,
|
|
21
|
+
useThreadHistory,
|
|
22
|
+
} from "./flatListScroll";
|
|
28
23
|
|
|
29
24
|
type MessageComponents =
|
|
30
25
|
| {
|
|
@@ -68,56 +63,18 @@ export type ThreadMessagesFlatListProps = Omit<
|
|
|
68
63
|
scrollToBottomOnRunStart?: boolean | undefined;
|
|
69
64
|
scrollToBottomOnInitialize?: boolean | undefined;
|
|
70
65
|
scrollToBottomOnThreadSwitch?: boolean | undefined;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
| undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Pages older messages in when the list nears its start. Defaults to the
|
|
68
|
+
* runtime's `thread.hasEarlier` / `isLoadingEarlier` / `loadEarlier()`;
|
|
69
|
+
* pass it to drive paging from app state instead.
|
|
70
|
+
*/
|
|
71
|
+
history?: FlatListHistory | undefined;
|
|
78
72
|
};
|
|
79
73
|
|
|
80
74
|
/** @deprecated Use ThreadMessagesFlatListProps instead. */
|
|
81
75
|
export type ThreadMessagesProps = ThreadMessagesFlatListProps;
|
|
82
76
|
|
|
83
77
|
const DEFAULT_SYSTEM_MESSAGE = () => null;
|
|
84
|
-
const AT_BOTTOM_THRESHOLD = 4;
|
|
85
|
-
|
|
86
|
-
const useHistoryLoad = (
|
|
87
|
-
history: ThreadMessagesFlatListProps["history"],
|
|
88
|
-
onStartReached: FlatListProps<ThreadMessage>["onStartReached"],
|
|
89
|
-
) => {
|
|
90
|
-
const loadRequestedRef = useRef(false);
|
|
91
|
-
const hasMore = history?.hasMore ?? false;
|
|
92
|
-
const isLoadingMore = history?.isLoadingMore ?? false;
|
|
93
|
-
|
|
94
|
-
// The latch is scoped to one commit so a no-op load cannot disable paging.
|
|
95
|
-
useEffect(() => {
|
|
96
|
-
loadRequestedRef.current = isLoadingMore;
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const handleStartReached = useCallback<
|
|
100
|
-
NonNullable<FlatListProps<ThreadMessage>["onStartReached"]>
|
|
101
|
-
>(
|
|
102
|
-
(info) => {
|
|
103
|
-
onStartReached?.(info);
|
|
104
|
-
if (loadRequestedRef.current) return;
|
|
105
|
-
loadRequestedRef.current = true;
|
|
106
|
-
try {
|
|
107
|
-
history?.loadMore();
|
|
108
|
-
} catch (error) {
|
|
109
|
-
loadRequestedRef.current = false;
|
|
110
|
-
throw error;
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
[history, onStartReached],
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
canLoadMore: hasMore && !isLoadingMore,
|
|
118
|
-
handleStartReached,
|
|
119
|
-
};
|
|
120
|
-
};
|
|
121
78
|
|
|
122
79
|
const getComponent = (
|
|
123
80
|
components: MessageComponents,
|
|
@@ -223,230 +180,6 @@ const ThreadMessageByChildren = memo(
|
|
|
223
180
|
);
|
|
224
181
|
ThreadMessageByChildren.displayName = "ThreadPrimitive.MessageByChildren";
|
|
225
182
|
|
|
226
|
-
const setForwardedRef = <T,>(ref: ForwardedRef<T>, value: T | null) => {
|
|
227
|
-
if (typeof ref === "function") {
|
|
228
|
-
ref(value);
|
|
229
|
-
} else if (ref) {
|
|
230
|
-
ref.current = value;
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
const useComposedFlatListRef = (
|
|
235
|
-
forwardedRef: ForwardedRef<FlatList<ThreadMessage>>,
|
|
236
|
-
) => {
|
|
237
|
-
const flatListRef = useRef<FlatList<ThreadMessage> | null>(null);
|
|
238
|
-
|
|
239
|
-
const setFlatListRef = useCallback(
|
|
240
|
-
(node: FlatList<ThreadMessage> | null) => {
|
|
241
|
-
flatListRef.current = node;
|
|
242
|
-
setForwardedRef(forwardedRef, node);
|
|
243
|
-
},
|
|
244
|
-
[forwardedRef],
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
return [flatListRef, setFlatListRef] as const;
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
const useThreadMessagesFlatListAutoScroll = ({
|
|
251
|
-
flatListRef,
|
|
252
|
-
hasMessages,
|
|
253
|
-
horizontal = false,
|
|
254
|
-
autoScroll = true,
|
|
255
|
-
scrollToBottomOnRunStart = true,
|
|
256
|
-
scrollToBottomOnInitialize = true,
|
|
257
|
-
scrollToBottomOnThreadSwitch = true,
|
|
258
|
-
}: {
|
|
259
|
-
flatListRef: RefObject<FlatList<ThreadMessage> | null>;
|
|
260
|
-
hasMessages: boolean;
|
|
261
|
-
horizontal?: boolean | null | undefined;
|
|
262
|
-
autoScroll?: boolean | undefined;
|
|
263
|
-
scrollToBottomOnRunStart?: boolean | undefined;
|
|
264
|
-
scrollToBottomOnInitialize?: boolean | undefined;
|
|
265
|
-
scrollToBottomOnThreadSwitch?: boolean | undefined;
|
|
266
|
-
}) => {
|
|
267
|
-
const metricsRef = useRef({
|
|
268
|
-
contentHeight: 0,
|
|
269
|
-
viewportHeight: 0,
|
|
270
|
-
scrollY: 0,
|
|
271
|
-
});
|
|
272
|
-
const isAtBottomRef = useRef(true);
|
|
273
|
-
const lastScrollEventOffsetRef = useRef(0);
|
|
274
|
-
const initializeScrollRequestedRef = useRef(false);
|
|
275
|
-
const contentSizeVersionRef = useRef(0);
|
|
276
|
-
const pendingScrollToBottomRef = useRef<
|
|
277
|
-
| false
|
|
278
|
-
| {
|
|
279
|
-
animated: boolean;
|
|
280
|
-
minimumContentSizeVersion: number;
|
|
281
|
-
}
|
|
282
|
-
>(false);
|
|
283
|
-
|
|
284
|
-
const updateIsAtBottom = useCallback(() => {
|
|
285
|
-
const { contentHeight, scrollY, viewportHeight } = metricsRef.current;
|
|
286
|
-
isAtBottomRef.current =
|
|
287
|
-
contentHeight <= viewportHeight ||
|
|
288
|
-
contentHeight - scrollY - viewportHeight <= AT_BOTTOM_THRESHOLD;
|
|
289
|
-
}, []);
|
|
290
|
-
|
|
291
|
-
// Commanding a scroll records the intended position immediately; the
|
|
292
|
-
// native scroll echo is bridged and throttled, so waiting for it lets a
|
|
293
|
-
// fast stream observe stale metrics and drop out of following.
|
|
294
|
-
const scrollToBottom = useCallback(
|
|
295
|
-
(animated: boolean) => {
|
|
296
|
-
const { contentHeight, viewportHeight } = metricsRef.current;
|
|
297
|
-
const offset = Math.max(0, contentHeight - viewportHeight);
|
|
298
|
-
metricsRef.current.scrollY = offset;
|
|
299
|
-
isAtBottomRef.current = true;
|
|
300
|
-
flatListRef.current?.scrollToOffset({ offset, animated });
|
|
301
|
-
},
|
|
302
|
-
[flatListRef],
|
|
303
|
-
);
|
|
304
|
-
|
|
305
|
-
const handleLayout = useCallback(
|
|
306
|
-
(event: LayoutChangeEvent) => {
|
|
307
|
-
const wasAtBottom = isAtBottomRef.current;
|
|
308
|
-
const previousViewportHeight = metricsRef.current.viewportHeight;
|
|
309
|
-
const viewportHeight = horizontal
|
|
310
|
-
? event.nativeEvent.layout.width
|
|
311
|
-
: event.nativeEvent.layout.height;
|
|
312
|
-
metricsRef.current.viewportHeight = viewportHeight;
|
|
313
|
-
updateIsAtBottom();
|
|
314
|
-
const pending = pendingScrollToBottomRef.current;
|
|
315
|
-
if (
|
|
316
|
-
pending &&
|
|
317
|
-
contentSizeVersionRef.current >= pending.minimumContentSizeVersion &&
|
|
318
|
-
viewportHeight > 0
|
|
319
|
-
) {
|
|
320
|
-
pendingScrollToBottomRef.current = false;
|
|
321
|
-
scrollToBottom(pending.animated);
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
if (!wasAtBottom) return;
|
|
325
|
-
// Layout changes are never user gestures, so they must not unpin. Past
|
|
326
|
-
// the first measurement, a viewport change while pinned re-commands the
|
|
327
|
-
// bottom position, since no content-size event follows a bare keyboard
|
|
328
|
-
// open or close.
|
|
329
|
-
if (
|
|
330
|
-
autoScroll &&
|
|
331
|
-
previousViewportHeight !== 0 &&
|
|
332
|
-
viewportHeight !== previousViewportHeight
|
|
333
|
-
) {
|
|
334
|
-
scrollToBottom(pending ? pending.animated : false);
|
|
335
|
-
} else {
|
|
336
|
-
isAtBottomRef.current = true;
|
|
337
|
-
}
|
|
338
|
-
},
|
|
339
|
-
[autoScroll, horizontal, scrollToBottom, updateIsAtBottom],
|
|
340
|
-
);
|
|
341
|
-
|
|
342
|
-
const handleScroll = useCallback(
|
|
343
|
-
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
344
|
-
const { contentOffset, contentSize, layoutMeasurement } =
|
|
345
|
-
event.nativeEvent;
|
|
346
|
-
const scrollOffset = horizontal ? contentOffset.x : contentOffset.y;
|
|
347
|
-
const previousEventOffset = lastScrollEventOffsetRef.current;
|
|
348
|
-
const wasPinnedToBottom = isAtBottomRef.current;
|
|
349
|
-
lastScrollEventOffsetRef.current = scrollOffset;
|
|
350
|
-
metricsRef.current = {
|
|
351
|
-
contentHeight: horizontal ? contentSize.width : contentSize.height,
|
|
352
|
-
viewportHeight: horizontal
|
|
353
|
-
? layoutMeasurement.width
|
|
354
|
-
: layoutMeasurement.height,
|
|
355
|
-
scrollY: scrollOffset,
|
|
356
|
-
};
|
|
357
|
-
updateIsAtBottom();
|
|
358
|
-
const upwardMove = scrollOffset < previousEventOffset;
|
|
359
|
-
// Only a deliberate upward move unpins or cancels a pending scroll.
|
|
360
|
-
// Gestures are detected echo-to-echo because a commanded scroll
|
|
361
|
-
// optimistically moves the tracked position ahead of its ascending
|
|
362
|
-
// animation echoes, and those echoes must not unpin mid-flight.
|
|
363
|
-
if (wasPinnedToBottom && !upwardMove) {
|
|
364
|
-
isAtBottomRef.current = true;
|
|
365
|
-
}
|
|
366
|
-
if (!isAtBottomRef.current && upwardMove) {
|
|
367
|
-
pendingScrollToBottomRef.current = false;
|
|
368
|
-
}
|
|
369
|
-
},
|
|
370
|
-
[horizontal, updateIsAtBottom],
|
|
371
|
-
);
|
|
372
|
-
|
|
373
|
-
const handleContentSizeChange = useCallback(
|
|
374
|
-
(width: number, height: number) => {
|
|
375
|
-
const metrics = metricsRef.current;
|
|
376
|
-
const contentHeight = horizontal ? width : height;
|
|
377
|
-
const previousContentHeight = metrics.contentHeight;
|
|
378
|
-
const wasAtBottom = isAtBottomRef.current;
|
|
379
|
-
if (contentHeight > 0) contentSizeVersionRef.current += 1;
|
|
380
|
-
metrics.contentHeight = contentHeight;
|
|
381
|
-
updateIsAtBottom();
|
|
382
|
-
|
|
383
|
-
// Initialize and thread-switch requests are repeated after the list has
|
|
384
|
-
// measured so the explicit bottom offset uses real content metrics.
|
|
385
|
-
const pendingScroll = pendingScrollToBottomRef.current;
|
|
386
|
-
if (
|
|
387
|
-
pendingScroll &&
|
|
388
|
-
contentSizeVersionRef.current >=
|
|
389
|
-
pendingScroll.minimumContentSizeVersion &&
|
|
390
|
-
metrics.viewportHeight > 0
|
|
391
|
-
) {
|
|
392
|
-
pendingScrollToBottomRef.current = false;
|
|
393
|
-
scrollToBottom(pendingScroll.animated);
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (!autoScroll) return;
|
|
398
|
-
if (!wasAtBottom) return;
|
|
399
|
-
if (previousContentHeight === 0) return;
|
|
400
|
-
if (contentHeight <= previousContentHeight) return;
|
|
401
|
-
|
|
402
|
-
scrollToBottom(false);
|
|
403
|
-
},
|
|
404
|
-
[autoScroll, horizontal, scrollToBottom, updateIsAtBottom],
|
|
405
|
-
);
|
|
406
|
-
|
|
407
|
-
useEffect(() => {
|
|
408
|
-
if (!scrollToBottomOnInitialize) return;
|
|
409
|
-
if (!hasMessages) {
|
|
410
|
-
initializeScrollRequestedRef.current = false;
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
if (initializeScrollRequestedRef.current) return;
|
|
414
|
-
|
|
415
|
-
initializeScrollRequestedRef.current = true;
|
|
416
|
-
pendingScrollToBottomRef.current = {
|
|
417
|
-
animated: false,
|
|
418
|
-
minimumContentSizeVersion: Math.max(1, contentSizeVersionRef.current),
|
|
419
|
-
};
|
|
420
|
-
scrollToBottom(false);
|
|
421
|
-
}, [hasMessages, scrollToBottom, scrollToBottomOnInitialize]);
|
|
422
|
-
|
|
423
|
-
useAuiEvent("thread.runStart", () => {
|
|
424
|
-
if (!scrollToBottomOnRunStart) return;
|
|
425
|
-
pendingScrollToBottomRef.current = {
|
|
426
|
-
animated: true,
|
|
427
|
-
minimumContentSizeVersion: contentSizeVersionRef.current + 1,
|
|
428
|
-
};
|
|
429
|
-
scrollToBottom(true);
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
useAuiEvent("threads.selectionChanged", () => {
|
|
433
|
-
if (!scrollToBottomOnThreadSwitch) return;
|
|
434
|
-
initializeScrollRequestedRef.current = false;
|
|
435
|
-
lastScrollEventOffsetRef.current = 0;
|
|
436
|
-
pendingScrollToBottomRef.current = {
|
|
437
|
-
animated: false,
|
|
438
|
-
minimumContentSizeVersion: contentSizeVersionRef.current + 1,
|
|
439
|
-
};
|
|
440
|
-
scrollToBottom(false);
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
return {
|
|
444
|
-
handleLayout,
|
|
445
|
-
handleScroll,
|
|
446
|
-
handleContentSizeChange,
|
|
447
|
-
};
|
|
448
|
-
};
|
|
449
|
-
|
|
450
183
|
export const ThreadMessagesFlatList = forwardRef<
|
|
451
184
|
FlatList<ThreadMessage>,
|
|
452
185
|
ThreadMessagesFlatListProps
|
|
@@ -461,6 +194,8 @@ export const ThreadMessagesFlatList = forwardRef<
|
|
|
461
194
|
onScroll,
|
|
462
195
|
onStartReached,
|
|
463
196
|
onStartReachedThreshold,
|
|
197
|
+
onEndReached,
|
|
198
|
+
onEndReachedThreshold,
|
|
464
199
|
scrollEventThrottle,
|
|
465
200
|
scrollToBottomOnInitialize,
|
|
466
201
|
scrollToBottomOnRunStart,
|
|
@@ -471,12 +206,13 @@ export const ThreadMessagesFlatList = forwardRef<
|
|
|
471
206
|
forwardedRef,
|
|
472
207
|
) => {
|
|
473
208
|
const messages = useAuiState((s) => s.thread.messages);
|
|
209
|
+
const effectiveHistory = useThreadHistory(history);
|
|
474
210
|
const [flatListRef, setFlatListRef] = useComposedFlatListRef(forwardedRef);
|
|
475
211
|
const {
|
|
476
212
|
handleContentSizeChange: handleAutoScrollContentSizeChange,
|
|
477
213
|
handleLayout: handleAutoScrollLayout,
|
|
478
214
|
handleScroll: handleAutoScrollScroll,
|
|
479
|
-
} =
|
|
215
|
+
} = useFlatListAutoScroll({
|
|
480
216
|
flatListRef,
|
|
481
217
|
hasMessages: messages.length > 0,
|
|
482
218
|
horizontal: flatListProps.horizontal,
|
|
@@ -502,39 +238,35 @@ export const ThreadMessagesFlatList = forwardRef<
|
|
|
502
238
|
|
|
503
239
|
const keyExtractor = useCallback((item: ThreadMessage) => item.id, []);
|
|
504
240
|
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
);
|
|
241
|
+
const scrollProps = useFlatListScrollProps({
|
|
242
|
+
autoScroll,
|
|
243
|
+
scrollToBottomOnInitialize,
|
|
244
|
+
scrollToBottomOnRunStart,
|
|
245
|
+
scrollToBottomOnThreadSwitch,
|
|
246
|
+
onContentSizeChange,
|
|
247
|
+
onLayout,
|
|
248
|
+
onScroll,
|
|
249
|
+
scrollEventThrottle,
|
|
250
|
+
handleAutoScrollContentSizeChange,
|
|
251
|
+
handleAutoScrollLayout,
|
|
252
|
+
handleAutoScrollScroll,
|
|
253
|
+
});
|
|
518
254
|
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
onScroll?.(event);
|
|
523
|
-
},
|
|
524
|
-
[handleAutoScrollScroll, onScroll],
|
|
255
|
+
const { canLoadMore, handleReached } = useHistoryLoad(
|
|
256
|
+
effectiveHistory,
|
|
257
|
+
onStartReached,
|
|
525
258
|
);
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
259
|
+
const pagingProps = getFlatListPagingProps(
|
|
260
|
+
"start",
|
|
261
|
+
effectiveHistory,
|
|
262
|
+
canLoadMore,
|
|
263
|
+
handleReached,
|
|
264
|
+
{
|
|
265
|
+
onStartReached,
|
|
266
|
+
onStartReachedThreshold,
|
|
267
|
+
onEndReached,
|
|
268
|
+
onEndReachedThreshold,
|
|
531
269
|
},
|
|
532
|
-
[handleAutoScrollContentSizeChange, onContentSizeChange],
|
|
533
|
-
);
|
|
534
|
-
|
|
535
|
-
const { canLoadMore, handleStartReached } = useHistoryLoad(
|
|
536
|
-
history,
|
|
537
|
-
onStartReached,
|
|
538
270
|
);
|
|
539
271
|
|
|
540
272
|
return (
|
|
@@ -544,34 +276,8 @@ export const ThreadMessagesFlatList = forwardRef<
|
|
|
544
276
|
renderItem={renderItem}
|
|
545
277
|
keyExtractor={keyExtractor}
|
|
546
278
|
maintainVisibleContentPosition={{ minIndexForVisible: 0 }}
|
|
547
|
-
{...
|
|
548
|
-
|
|
549
|
-
onContentSizeChange: handleContentSizeChange,
|
|
550
|
-
onLayout: handleLayout,
|
|
551
|
-
onScroll: handleScroll,
|
|
552
|
-
scrollEventThrottle: scrollEventThrottle ?? 16,
|
|
553
|
-
}
|
|
554
|
-
: {
|
|
555
|
-
...(onContentSizeChange && { onContentSizeChange }),
|
|
556
|
-
...(onLayout && { onLayout }),
|
|
557
|
-
...(onScroll && { onScroll }),
|
|
558
|
-
...(scrollEventThrottle !== undefined && { scrollEventThrottle }),
|
|
559
|
-
})}
|
|
560
|
-
{...(history
|
|
561
|
-
? {
|
|
562
|
-
...(canLoadMore
|
|
563
|
-
? { onStartReached: handleStartReached }
|
|
564
|
-
: onStartReached
|
|
565
|
-
? { onStartReached }
|
|
566
|
-
: {}),
|
|
567
|
-
onStartReachedThreshold: onStartReachedThreshold ?? 1,
|
|
568
|
-
}
|
|
569
|
-
: {
|
|
570
|
-
...(onStartReached && { onStartReached }),
|
|
571
|
-
...(onStartReachedThreshold !== undefined && {
|
|
572
|
-
onStartReachedThreshold,
|
|
573
|
-
}),
|
|
574
|
-
})}
|
|
279
|
+
{...scrollProps}
|
|
280
|
+
{...pagingProps}
|
|
575
281
|
{...flatListProps}
|
|
576
282
|
/>
|
|
577
283
|
);
|