@assistant-ui/react-native 0.1.43 → 0.1.44
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/context/AssistantContext.d.ts +2 -2
- package/dist/context/AssistantContext.d.ts.map +1 -0
- package/dist/context/providers/AttachmentByIndexProvider.d.ts +2 -2
- package/dist/context/providers/AttachmentByIndexProvider.d.ts.map +1 -0
- package/dist/context/providers/RuntimeAdapterProvider.d.ts +2 -2
- package/dist/context/providers/RuntimeAdapterProvider.d.ts.map +1 -0
- package/dist/context/providers/ThreadListItemRuntimeProvider.d.ts +2 -2
- package/dist/context/providers/ThreadListItemRuntimeProvider.d.ts.map +1 -0
- package/dist/index.d.ts +54 -23
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -4
- package/dist/internal.d.ts +4 -3
- package/dist/internal.d.ts.map +1 -0
- package/dist/primitives/actionBar/ActionBarCopy.d.ts +7 -9
- package/dist/primitives/actionBar/ActionBarCopy.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarEdit.d.ts +5 -7
- package/dist/primitives/actionBar/ActionBarEdit.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.d.ts +6 -8
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +28 -20
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.d.ts +6 -8
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +28 -20
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarReload.d.ts +5 -7
- package/dist/primitives/actionBar/ActionBarReload.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarSpeak.d.ts +7 -9
- package/dist/primitives/actionBar/ActionBarSpeak.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts +7 -9
- package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts.map +1 -1
- package/dist/primitives/actionBar.d.ts +7 -12
- package/dist/primitives/actionBar.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentName.d.ts +2 -4
- package/dist/primitives/attachment/AttachmentName.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentRemove.d.ts +2 -4
- package/dist/primitives/attachment/AttachmentRemove.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentRoot.d.ts +3 -5
- package/dist/primitives/attachment/AttachmentRoot.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentThumb.d.ts +2 -4
- package/dist/primitives/attachment/AttachmentThumb.d.ts.map +1 -1
- package/dist/primitives/attachment.d.ts +4 -9
- package/dist/primitives/attachment.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerCount.d.ts +1 -3
- package/dist/primitives/branchPicker/BranchPickerCount.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerNext.d.ts +5 -7
- package/dist/primitives/branchPicker/BranchPickerNext.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerNumber.d.ts +1 -3
- package/dist/primitives/branchPicker/BranchPickerNumber.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerPrevious.d.ts +5 -7
- package/dist/primitives/branchPicker/BranchPickerPrevious.d.ts.map +1 -1
- package/dist/primitives/branchPicker.d.ts +4 -9
- package/dist/primitives/branchPicker.d.ts.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.d.ts +2 -4
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.d.ts.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtRoot.d.ts +3 -5
- package/dist/primitives/chainOfThought/ChainOfThoughtRoot.d.ts.map +1 -1
- package/dist/primitives/chainOfThought.d.ts +3 -8
- package/dist/primitives/chainOfThought.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.d.ts +2 -4
- package/dist/primitives/composer/ComposerAddAttachment.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAttachments.d.ts +1 -3
- package/dist/primitives/composer/ComposerAttachments.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerCancel.d.ts +2 -4
- package/dist/primitives/composer/ComposerCancel.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerIf.d.ts +2 -2
- package/dist/primitives/composer/ComposerIf.d.ts.map +1 -0
- package/dist/primitives/composer/ComposerInput.d.ts +8 -10
- package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +1 -1
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerQueue.d.ts +2 -2
- package/dist/primitives/composer/ComposerQueue.d.ts.map +1 -0
- package/dist/primitives/composer/ComposerQuote.d.ts +3 -5
- package/dist/primitives/composer/ComposerQuote.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerQuoteDismiss.d.ts +2 -4
- package/dist/primitives/composer/ComposerQuoteDismiss.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerQuoteText.d.ts +3 -5
- package/dist/primitives/composer/ComposerQuoteText.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerRoot.d.ts +3 -5
- package/dist/primitives/composer/ComposerRoot.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerSend.d.ts +2 -4
- package/dist/primitives/composer/ComposerSend.d.ts.map +1 -1
- package/dist/primitives/composer.d.ts +11 -16
- package/dist/primitives/composer.d.ts.map +1 -1
- package/dist/primitives/error/ErrorMessage.d.ts +5 -7
- package/dist/primitives/error/ErrorMessage.d.ts.map +1 -1
- package/dist/primitives/error/ErrorRoot.d.ts +5 -7
- package/dist/primitives/error/ErrorRoot.d.ts.map +1 -1
- package/dist/primitives/error/index.d.ts +2 -7
- package/dist/primitives/error/index.d.ts.map +1 -1
- package/dist/primitives/message/MessageAttachments.d.ts +1 -3
- package/dist/primitives/message/MessageAttachments.d.ts.map +1 -1
- package/dist/primitives/message/MessageContent.d.ts +46 -47
- package/dist/primitives/message/MessageContent.d.ts.map +1 -1
- package/dist/primitives/message/MessageContent.js +27 -21
- package/dist/primitives/message/MessageContent.js.map +1 -1
- package/dist/primitives/message/MessageIf.d.ts +7 -9
- package/dist/primitives/message/MessageIf.d.ts.map +1 -1
- package/dist/primitives/message/MessageParts.d.ts +4 -6
- package/dist/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/primitives/message/MessageRoot.d.ts +3 -5
- package/dist/primitives/message/MessageRoot.d.ts.map +1 -1
- package/dist/primitives/message.d.ts +6 -11
- package/dist/primitives/message.d.ts.map +1 -1
- package/dist/primitives/messagePart/MessagePartImage.d.ts +4 -6
- package/dist/primitives/messagePart/MessagePartImage.d.ts.map +1 -1
- package/dist/primitives/messagePart/MessagePartInProgress.d.ts +2 -2
- package/dist/primitives/messagePart/MessagePartInProgress.d.ts.map +1 -0
- package/dist/primitives/messagePart/MessagePartText.d.ts +4 -6
- package/dist/primitives/messagePart/MessagePartText.d.ts.map +1 -1
- package/dist/primitives/messagePart.d.ts +4 -9
- package/dist/primitives/messagePart.d.ts.map +1 -1
- package/dist/primitives/queueItem/QueueItemRemove.d.ts +2 -4
- package/dist/primitives/queueItem/QueueItemRemove.d.ts.map +1 -1
- package/dist/primitives/queueItem/QueueItemSteer.d.ts +2 -4
- package/dist/primitives/queueItem/QueueItemSteer.d.ts.map +1 -1
- package/dist/primitives/queueItem/QueueItemText.d.ts +3 -5
- package/dist/primitives/queueItem/QueueItemText.d.ts.map +1 -1
- package/dist/primitives/queueItem.d.ts +3 -8
- package/dist/primitives/queueItem.d.ts.map +1 -1
- package/dist/primitives/suggestion/SuggestionDescription.d.ts +3 -5
- package/dist/primitives/suggestion/SuggestionDescription.d.ts.map +1 -1
- package/dist/primitives/suggestion/SuggestionTitle.d.ts +3 -5
- package/dist/primitives/suggestion/SuggestionTitle.d.ts.map +1 -1
- package/dist/primitives/suggestion/SuggestionTrigger.d.ts +13 -15
- package/dist/primitives/suggestion/SuggestionTrigger.d.ts.map +1 -1
- package/dist/primitives/suggestion/SuggestionTrigger.js +1 -1
- package/dist/primitives/suggestion.d.ts +3 -8
- package/dist/primitives/suggestion.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadEmpty.d.ts +2 -4
- package/dist/primitives/thread/ThreadEmpty.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadIf.d.ts +4 -6
- package/dist/primitives/thread/ThreadIf.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadMessages.d.ts +37 -37
- package/dist/primitives/thread/ThreadMessages.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadMessages.js +70 -58
- package/dist/primitives/thread/ThreadMessages.js.map +1 -1
- package/dist/primitives/thread/ThreadRoot.d.ts +3 -5
- package/dist/primitives/thread/ThreadRoot.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadSuggestion.d.ts +15 -17
- package/dist/primitives/thread/ThreadSuggestion.d.ts.map +1 -1
- package/dist/primitives/thread.d.ts +8 -11
- package/dist/primitives/thread.d.ts.map +1 -1
- package/dist/primitives/threadList/ThreadListItems.d.ts +6 -8
- package/dist/primitives/threadList/ThreadListItems.d.ts.map +1 -1
- package/dist/primitives/threadList/ThreadListItems.js +1 -1
- package/dist/primitives/threadList/ThreadListNew.d.ts +5 -7
- package/dist/primitives/threadList/ThreadListNew.d.ts.map +1 -1
- package/dist/primitives/threadList/ThreadListNew.js +21 -17
- package/dist/primitives/threadList/ThreadListNew.js.map +1 -1
- package/dist/primitives/threadList/ThreadListRoot.d.ts +3 -5
- package/dist/primitives/threadList/ThreadListRoot.d.ts.map +1 -1
- package/dist/primitives/threadList.d.ts +3 -8
- package/dist/primitives/threadList.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemArchive.d.ts +2 -4
- package/dist/primitives/threadListItem/ThreadListItemArchive.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemDelete.d.ts +2 -4
- package/dist/primitives/threadListItem/ThreadListItemDelete.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemRoot.d.ts +3 -5
- package/dist/primitives/threadListItem/ThreadListItemRoot.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts +5 -7
- package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js +21 -17
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts +2 -4
- package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts.map +1 -1
- package/dist/primitives/threadListItem.d.ts +6 -11
- package/dist/primitives/threadListItem.d.ts.map +1 -1
- package/dist/runtimes/useLocalRuntime.d.ts +2 -2
- package/dist/runtimes/useLocalRuntime.d.ts.map +1 -0
- package/dist/runtimes/useRemoteThreadListRuntime.d.ts +2 -2
- package/dist/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -0
- package/package.json +7 -7
- package/src/index.ts +90 -1
- package/src/primitives/actionBar/ActionBar.test.tsx +76 -0
- package/src/primitives/actionBar/ActionBarFeedbackNegative.tsx +4 -1
- package/src/primitives/actionBar/ActionBarFeedbackPositive.tsx +4 -1
- package/src/primitives/composer/ComposerInput.test.tsx +17 -1
- package/src/primitives/composer/ComposerInput.tsx +6 -1
- package/src/primitives/message/MessageContent.test.tsx +64 -3
- package/src/primitives/message/MessageContent.tsx +5 -3
- package/src/primitives/thread/ThreadMessages.test.tsx +111 -5
- package/src/primitives/thread/ThreadMessages.tsx +41 -11
- package/src/primitives/threadList/ThreadListNew.test.tsx +35 -8
- package/src/primitives/threadList/ThreadListNew.tsx +4 -1
- package/src/primitives/threadListItem/ThreadListItemTrigger.test.tsx +32 -6
- package/src/primitives/threadListItem/ThreadListItemTrigger.tsx +4 -1
|
@@ -81,6 +81,9 @@ const ToolUIDisplay = ({
|
|
|
81
81
|
addResult={partMethods.addToolResult}
|
|
82
82
|
resume={partMethods.resumeToolCall}
|
|
83
83
|
respondToApproval={partMethods.respondToToolApproval}
|
|
84
|
+
{...(partMethods.unstable_recordInteraction && {
|
|
85
|
+
unstable_recordInteraction: partMethods.unstable_recordInteraction,
|
|
86
|
+
})}
|
|
84
87
|
/>
|
|
85
88
|
);
|
|
86
89
|
}
|
|
@@ -103,9 +106,8 @@ const DataUIDisplay = ({
|
|
|
103
106
|
index: number;
|
|
104
107
|
}) => {
|
|
105
108
|
const Render = useAuiState((s) => {
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
return renders;
|
|
109
|
+
const named = s.dataRenderers.renderers[part.name]?.[0];
|
|
110
|
+
return named ?? s.dataRenderers.fallbacks[0];
|
|
109
111
|
});
|
|
110
112
|
if (Render) return <Render {...(part as DataMessagePartProps)} />;
|
|
111
113
|
if (Fallback) return <Fallback part={part} index={index} />;
|
|
@@ -3,6 +3,7 @@ import { createRoot, type Root } from "react-dom/client";
|
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
import type { FlatList, FlatListProps } from "react-native";
|
|
5
5
|
import type { ThreadMessage } from "@assistant-ui/core";
|
|
6
|
+
import type { MessageState } from "@assistant-ui/core/store";
|
|
6
7
|
import { ThreadMessages, ThreadMessagesFlatList } from "./ThreadMessages";
|
|
7
8
|
|
|
8
9
|
type Msg = { id: string; role: string };
|
|
@@ -12,7 +13,7 @@ const h = vi.hoisted(() => ({
|
|
|
12
13
|
thread: { messages: [] as Msg[] },
|
|
13
14
|
message: { role: "user" as string, composer: { isEditing: false } },
|
|
14
15
|
},
|
|
15
|
-
itemState: { role: "user" } as { role: string },
|
|
16
|
+
itemState: { role: "user" } as { role: string; parts?: unknown[] },
|
|
16
17
|
events: {} as Record<string, Set<() => void>>,
|
|
17
18
|
flatListProps: null as Record<string, unknown> | null,
|
|
18
19
|
scrollToOffset: vi.fn(),
|
|
@@ -303,13 +304,15 @@ describe("ThreadMessages", () => {
|
|
|
303
304
|
describe("children mode", () => {
|
|
304
305
|
it("renders via the children render prop", async () => {
|
|
305
306
|
h.state.thread.messages = [{ id: "1", role: "user" }];
|
|
306
|
-
h.itemState = { role: "user" };
|
|
307
|
-
const children = vi.fn(({ message }: { message:
|
|
308
|
-
<span data-testid="child">
|
|
307
|
+
h.itemState = { role: "user", parts: [] };
|
|
308
|
+
const children = vi.fn(({ message }: { message: MessageState }) => (
|
|
309
|
+
<span data-testid="child">
|
|
310
|
+
child:{message.role}:{message.parts.length}
|
|
311
|
+
</span>
|
|
309
312
|
));
|
|
310
313
|
await mount({ children });
|
|
311
314
|
const el = container.querySelector('[data-testid="child"]');
|
|
312
|
-
expect(el?.textContent).toBe("child:user");
|
|
315
|
+
expect(el?.textContent).toBe("child:user:0");
|
|
313
316
|
expect(children).toHaveBeenCalled();
|
|
314
317
|
});
|
|
315
318
|
});
|
|
@@ -420,6 +423,109 @@ describe("ThreadMessages", () => {
|
|
|
420
423
|
expect(getFlatListProps().onStartReached).toBeUndefined();
|
|
421
424
|
});
|
|
422
425
|
|
|
426
|
+
it("loads one page when start reached fires again before rerender", async () => {
|
|
427
|
+
const loadMore = vi.fn();
|
|
428
|
+
const callerOnStartReached = vi.fn();
|
|
429
|
+
|
|
430
|
+
await mountFlatList({
|
|
431
|
+
components: messageComponents,
|
|
432
|
+
history: { hasMore: true, isLoadingMore: false, loadMore },
|
|
433
|
+
onStartReached: callerOnStartReached,
|
|
434
|
+
});
|
|
435
|
+
const installedOnStartReached = getFlatListProps().onStartReached;
|
|
436
|
+
|
|
437
|
+
installedOnStartReached?.({ distanceFromStart: 0 });
|
|
438
|
+
installedOnStartReached?.({ distanceFromStart: 0 });
|
|
439
|
+
|
|
440
|
+
expect(loadMore).toHaveBeenCalledOnce();
|
|
441
|
+
expect(callerOnStartReached).toHaveBeenCalledTimes(2);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it("loads again after a commit without a loading transition", async () => {
|
|
445
|
+
const loadMore = vi.fn();
|
|
446
|
+
const history = { hasMore: true, isLoadingMore: false, loadMore };
|
|
447
|
+
|
|
448
|
+
await mountFlatList({ components: messageComponents, history });
|
|
449
|
+
getFlatListProps().onStartReached?.({ distanceFromStart: 0 });
|
|
450
|
+
|
|
451
|
+
await mountFlatList({ components: messageComponents, history });
|
|
452
|
+
getFlatListProps().onStartReached?.({ distanceFromStart: 0 });
|
|
453
|
+
|
|
454
|
+
expect(loadMore).toHaveBeenCalledTimes(2);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it("does not carry a request latch to another history source", async () => {
|
|
458
|
+
const firstLoadMore = vi.fn();
|
|
459
|
+
const secondLoadMore = vi.fn();
|
|
460
|
+
|
|
461
|
+
await mountFlatList({
|
|
462
|
+
components: messageComponents,
|
|
463
|
+
history: {
|
|
464
|
+
hasMore: true,
|
|
465
|
+
isLoadingMore: false,
|
|
466
|
+
loadMore: firstLoadMore,
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
getFlatListProps().onStartReached?.({ distanceFromStart: 0 });
|
|
470
|
+
|
|
471
|
+
await mountFlatList({
|
|
472
|
+
components: messageComponents,
|
|
473
|
+
history: {
|
|
474
|
+
hasMore: true,
|
|
475
|
+
isLoadingMore: false,
|
|
476
|
+
loadMore: secondLoadMore,
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
getFlatListProps().onStartReached?.({ distanceFromStart: 0 });
|
|
480
|
+
|
|
481
|
+
expect(firstLoadMore).toHaveBeenCalledOnce();
|
|
482
|
+
expect(secondLoadMore).toHaveBeenCalledOnce();
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it("loads again after the previous history request settles", async () => {
|
|
486
|
+
const loadMore = vi.fn();
|
|
487
|
+
|
|
488
|
+
await mountFlatList({
|
|
489
|
+
components: messageComponents,
|
|
490
|
+
history: { hasMore: true, isLoadingMore: false, loadMore },
|
|
491
|
+
});
|
|
492
|
+
getFlatListProps().onStartReached?.({ distanceFromStart: 0 });
|
|
493
|
+
|
|
494
|
+
await mountFlatList({
|
|
495
|
+
components: messageComponents,
|
|
496
|
+
history: { hasMore: true, isLoadingMore: true, loadMore },
|
|
497
|
+
});
|
|
498
|
+
await mountFlatList({
|
|
499
|
+
components: messageComponents,
|
|
500
|
+
history: { hasMore: true, isLoadingMore: false, loadMore },
|
|
501
|
+
});
|
|
502
|
+
getFlatListProps().onStartReached?.({ distanceFromStart: 0 });
|
|
503
|
+
|
|
504
|
+
expect(loadMore).toHaveBeenCalledTimes(2);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it("allows retrying when loadMore throws synchronously", async () => {
|
|
508
|
+
const loadError = new Error("load failed");
|
|
509
|
+
const loadMore = vi
|
|
510
|
+
.fn()
|
|
511
|
+
.mockImplementationOnce(() => {
|
|
512
|
+
throw loadError;
|
|
513
|
+
})
|
|
514
|
+
.mockImplementationOnce(() => undefined);
|
|
515
|
+
|
|
516
|
+
await mountFlatList({
|
|
517
|
+
components: messageComponents,
|
|
518
|
+
history: { hasMore: true, isLoadingMore: false, loadMore },
|
|
519
|
+
});
|
|
520
|
+
const onStartReached = getFlatListProps().onStartReached;
|
|
521
|
+
|
|
522
|
+
expect(() => onStartReached?.({ distanceFromStart: 0 })).toThrow(
|
|
523
|
+
loadError,
|
|
524
|
+
);
|
|
525
|
+
expect(() => onStartReached?.({ distanceFromStart: 0 })).not.toThrow();
|
|
526
|
+
expect(loadMore).toHaveBeenCalledTimes(2);
|
|
527
|
+
});
|
|
528
|
+
|
|
423
529
|
it("defaults the history threshold and preserves a caller override", async () => {
|
|
424
530
|
const history = {
|
|
425
531
|
hasMore: true,
|
|
@@ -17,7 +17,8 @@ import {
|
|
|
17
17
|
type NativeScrollEvent,
|
|
18
18
|
type NativeSyntheticEvent,
|
|
19
19
|
} from "react-native";
|
|
20
|
-
import type {
|
|
20
|
+
import type { ThreadMessage } from "@assistant-ui/core";
|
|
21
|
+
import type { MessageState } from "@assistant-ui/core/store";
|
|
21
22
|
import {
|
|
22
23
|
RenderChildrenWithAccessor,
|
|
23
24
|
useAuiEvent,
|
|
@@ -82,6 +83,42 @@ export type ThreadMessagesProps = ThreadMessagesFlatListProps;
|
|
|
82
83
|
const DEFAULT_SYSTEM_MESSAGE = () => null;
|
|
83
84
|
const AT_BOTTOM_THRESHOLD = 4;
|
|
84
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
|
+
|
|
85
122
|
const getComponent = (
|
|
86
123
|
components: MessageComponents,
|
|
87
124
|
role: ThreadMessage["role"],
|
|
@@ -495,16 +532,9 @@ export const ThreadMessagesFlatList = forwardRef<
|
|
|
495
532
|
[handleAutoScrollContentSizeChange, onContentSizeChange],
|
|
496
533
|
);
|
|
497
534
|
|
|
498
|
-
const canLoadMore
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
NonNullable<FlatListProps<ThreadMessage>["onStartReached"]>
|
|
502
|
-
>(
|
|
503
|
-
(info) => {
|
|
504
|
-
onStartReached?.(info);
|
|
505
|
-
history?.loadMore();
|
|
506
|
-
},
|
|
507
|
-
[history, onStartReached],
|
|
535
|
+
const { canLoadMore, handleStartReached } = useHistoryLoad(
|
|
536
|
+
history,
|
|
537
|
+
onStartReached,
|
|
508
538
|
);
|
|
509
539
|
|
|
510
540
|
return (
|
|
@@ -6,6 +6,7 @@ import { ThreadListNew } from "./ThreadListNew";
|
|
|
6
6
|
const h = vi.hoisted(() => ({
|
|
7
7
|
switchToNewThread: vi.fn<() => void>(),
|
|
8
8
|
state: { threads: { newThreadId: "new", mainThreadId: "other" } },
|
|
9
|
+
platform: { os: "web" },
|
|
9
10
|
pressableProps: null as Record<string, unknown> | null,
|
|
10
11
|
}));
|
|
11
12
|
|
|
@@ -41,7 +42,16 @@ vi.mock("react-native", async (importOriginal) => {
|
|
|
41
42
|
);
|
|
42
43
|
});
|
|
43
44
|
|
|
44
|
-
return {
|
|
45
|
+
return {
|
|
46
|
+
...actual,
|
|
47
|
+
Platform: {
|
|
48
|
+
...actual.Platform,
|
|
49
|
+
get OS() {
|
|
50
|
+
return h.platform.os;
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
Pressable: PressableMock,
|
|
54
|
+
};
|
|
45
55
|
});
|
|
46
56
|
|
|
47
57
|
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
@@ -58,6 +68,7 @@ describe("ThreadListNew", () => {
|
|
|
58
68
|
beforeEach(() => {
|
|
59
69
|
h.switchToNewThread.mockReset();
|
|
60
70
|
h.state.threads = { newThreadId: "new", mainThreadId: "other" };
|
|
71
|
+
h.platform.os = "web";
|
|
61
72
|
h.pressableProps = null;
|
|
62
73
|
|
|
63
74
|
container = document.createElement("div");
|
|
@@ -110,39 +121,55 @@ describe("ThreadListNew", () => {
|
|
|
110
121
|
expect(h.switchToNewThread).not.toHaveBeenCalled();
|
|
111
122
|
});
|
|
112
123
|
|
|
113
|
-
it("marks itself
|
|
124
|
+
it("marks itself current on the web while the new thread is current", async () => {
|
|
114
125
|
h.state.threads = { newThreadId: "new", mainThreadId: "new" };
|
|
115
126
|
|
|
116
|
-
await mount();
|
|
127
|
+
const el = await mount();
|
|
117
128
|
|
|
129
|
+
expect(el.getAttribute("aria-current")).toBe("true");
|
|
130
|
+
expect(el.hasAttribute("aria-selected")).toBe(false);
|
|
118
131
|
expect(accessibilityState()).toMatchObject({ selected: true });
|
|
119
132
|
});
|
|
120
133
|
|
|
121
|
-
it("is not
|
|
122
|
-
await mount();
|
|
134
|
+
it("is not current while another thread is current", async () => {
|
|
135
|
+
const el = await mount();
|
|
123
136
|
|
|
137
|
+
expect(el.hasAttribute("aria-current")).toBe(false);
|
|
124
138
|
expect(accessibilityState()).toMatchObject({ selected: false });
|
|
125
139
|
});
|
|
126
140
|
|
|
141
|
+
it("marks itself selected without aria-current on native", async () => {
|
|
142
|
+
h.platform.os = "ios";
|
|
143
|
+
h.state.threads = { newThreadId: "new", mainThreadId: "new" };
|
|
144
|
+
|
|
145
|
+
const el = await mount();
|
|
146
|
+
|
|
147
|
+
expect(el.hasAttribute("aria-current")).toBe(false);
|
|
148
|
+
expect(accessibilityState()).toMatchObject({ selected: true });
|
|
149
|
+
});
|
|
150
|
+
|
|
127
151
|
it("keeps other accessibility state the caller passes", async () => {
|
|
128
152
|
h.state.threads = { newThreadId: "new", mainThreadId: "new" };
|
|
129
153
|
|
|
130
|
-
await mount({ accessibilityState: { busy: true } });
|
|
154
|
+
const el = await mount({ accessibilityState: { busy: true } });
|
|
131
155
|
|
|
156
|
+
expect(el.getAttribute("aria-current")).toBe("true");
|
|
132
157
|
expect(accessibilityState()).toMatchObject({ selected: true, busy: true });
|
|
133
158
|
});
|
|
134
159
|
|
|
135
160
|
it("lets the caller override the selected state", async () => {
|
|
136
161
|
h.state.threads = { newThreadId: "new", mainThreadId: "new" };
|
|
137
162
|
|
|
138
|
-
await mount({ accessibilityState: { selected: false } });
|
|
163
|
+
const el = await mount({ accessibilityState: { selected: false } });
|
|
139
164
|
|
|
165
|
+
expect(el.hasAttribute("aria-current")).toBe(false);
|
|
140
166
|
expect(accessibilityState()).toMatchObject({ selected: false });
|
|
141
167
|
});
|
|
142
168
|
|
|
143
169
|
it("lets the caller select a control the state reports inactive", async () => {
|
|
144
|
-
await mount({ accessibilityState: { selected: true } });
|
|
170
|
+
const el = await mount({ accessibilityState: { selected: true } });
|
|
145
171
|
|
|
172
|
+
expect(el.getAttribute("aria-current")).toBe("true");
|
|
146
173
|
expect(accessibilityState()).toMatchObject({ selected: true });
|
|
147
174
|
});
|
|
148
175
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import {
|
|
3
|
+
Platform,
|
|
3
4
|
Pressable,
|
|
4
5
|
type PressableProps,
|
|
5
6
|
type PressableStateCallbackType,
|
|
@@ -27,12 +28,14 @@ export const ThreadListNew = ({
|
|
|
27
28
|
(s) => s.threads.newThreadId === s.threads.mainThreadId,
|
|
28
29
|
);
|
|
29
30
|
const { switchToNewThread } = useThreadListNew();
|
|
31
|
+
const selected = accessibilityState?.selected ?? isActive;
|
|
30
32
|
|
|
31
33
|
return (
|
|
32
34
|
<Pressable
|
|
33
35
|
onPress={switchToNewThread}
|
|
34
36
|
accessibilityRole="button"
|
|
35
|
-
accessibilityState={{
|
|
37
|
+
accessibilityState={{ ...accessibilityState, selected }}
|
|
38
|
+
aria-current={Platform.OS === "web" && selected ? "true" : undefined}
|
|
36
39
|
{...pressableProps}
|
|
37
40
|
>
|
|
38
41
|
{typeof children === "function"
|
|
@@ -9,6 +9,7 @@ const h = vi.hoisted(() => ({
|
|
|
9
9
|
threads: { mainThreadId: "thread-1" },
|
|
10
10
|
threadListItem: { id: "thread-1" },
|
|
11
11
|
},
|
|
12
|
+
platform: { os: "web" },
|
|
12
13
|
pressableProps: null as Record<string, unknown> | null,
|
|
13
14
|
}));
|
|
14
15
|
|
|
@@ -37,7 +38,16 @@ vi.mock("react-native", async (importOriginal) => {
|
|
|
37
38
|
{ ...props, ref },
|
|
38
39
|
);
|
|
39
40
|
});
|
|
40
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
...actual,
|
|
43
|
+
Platform: {
|
|
44
|
+
...actual.Platform,
|
|
45
|
+
get OS() {
|
|
46
|
+
return h.platform.os;
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
Pressable: PressableMock,
|
|
50
|
+
};
|
|
41
51
|
});
|
|
42
52
|
|
|
43
53
|
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
@@ -55,6 +65,7 @@ describe("ThreadListItemTrigger", () => {
|
|
|
55
65
|
h.switchTo.mockReset();
|
|
56
66
|
h.state.threads.mainThreadId = "thread-1";
|
|
57
67
|
h.state.threadListItem.id = "thread-1";
|
|
68
|
+
h.platform.os = "web";
|
|
58
69
|
h.pressableProps = null;
|
|
59
70
|
|
|
60
71
|
container = document.createElement("div");
|
|
@@ -107,23 +118,38 @@ describe("ThreadListItemTrigger", () => {
|
|
|
107
118
|
expect(h.switchTo).not.toHaveBeenCalled();
|
|
108
119
|
});
|
|
109
120
|
|
|
110
|
-
it("marks the current thread
|
|
111
|
-
await mount();
|
|
121
|
+
it("marks the current thread current on the web", async () => {
|
|
122
|
+
const el = await mount();
|
|
112
123
|
|
|
124
|
+
expect(el.getAttribute("aria-current")).toBe("true");
|
|
125
|
+
expect(el.hasAttribute("aria-selected")).toBe(false);
|
|
113
126
|
expect(accessibilityState()).toMatchObject({ selected: true });
|
|
114
127
|
});
|
|
115
128
|
|
|
116
|
-
it("does not mark another thread
|
|
129
|
+
it("does not mark another thread current", async () => {
|
|
117
130
|
h.state.threads.mainThreadId = "thread-2";
|
|
118
131
|
|
|
119
|
-
await mount();
|
|
132
|
+
const el = await mount();
|
|
120
133
|
|
|
134
|
+
expect(el.hasAttribute("aria-current")).toBe(false);
|
|
121
135
|
expect(accessibilityState()).toMatchObject({ selected: false });
|
|
122
136
|
});
|
|
123
137
|
|
|
138
|
+
it("marks the current thread selected without aria-current on native", async () => {
|
|
139
|
+
h.platform.os = "ios";
|
|
140
|
+
|
|
141
|
+
const el = await mount();
|
|
142
|
+
|
|
143
|
+
expect(el.hasAttribute("aria-current")).toBe(false);
|
|
144
|
+
expect(accessibilityState()).toMatchObject({ selected: true });
|
|
145
|
+
});
|
|
146
|
+
|
|
124
147
|
it("keeps caller accessibility state overrides", async () => {
|
|
125
|
-
await mount({
|
|
148
|
+
const el = await mount({
|
|
149
|
+
accessibilityState: { busy: true, selected: false },
|
|
150
|
+
});
|
|
126
151
|
|
|
152
|
+
expect(el.hasAttribute("aria-current")).toBe(false);
|
|
127
153
|
expect(accessibilityState()).toMatchObject({ busy: true, selected: false });
|
|
128
154
|
});
|
|
129
155
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import {
|
|
3
|
+
Platform,
|
|
3
4
|
Pressable,
|
|
4
5
|
type PressableProps,
|
|
5
6
|
type PressableStateCallbackType,
|
|
@@ -27,12 +28,14 @@ export const ThreadListItemTrigger = ({
|
|
|
27
28
|
(s) => s.threads.mainThreadId === s.threadListItem.id,
|
|
28
29
|
);
|
|
29
30
|
const { switchTo } = useThreadListItemTrigger();
|
|
31
|
+
const selected = accessibilityState?.selected ?? isActive;
|
|
30
32
|
|
|
31
33
|
return (
|
|
32
34
|
<Pressable
|
|
33
35
|
onPress={switchTo}
|
|
34
36
|
accessibilityRole="button"
|
|
35
|
-
accessibilityState={{
|
|
37
|
+
accessibilityState={{ ...accessibilityState, selected }}
|
|
38
|
+
aria-current={Platform.OS === "web" && selected ? "true" : undefined}
|
|
36
39
|
{...pressableProps}
|
|
37
40
|
>
|
|
38
41
|
{typeof children === "function"
|