@assistant-ui/react 0.15.24 → 0.15.26
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/hooks/useToolCallElapsed.d.ts +3 -1
- package/dist/hooks/useToolCallElapsed.d.ts.map +1 -1
- package/dist/hooks/useToolCallElapsed.js +8 -8
- package/dist/hooks/useToolCallElapsed.js.map +1 -1
- package/dist/index.d.ts +39 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.js +3 -2
- package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/mcp-apps/app-frame.d.ts.map +1 -1
- package/dist/mcp-apps/app-frame.js +56 -46
- package/dist/mcp-apps/app-frame.js.map +1 -1
- package/dist/mcp-apps/csp.d.ts +4 -0
- package/dist/mcp-apps/csp.d.ts.map +1 -0
- package/dist/mcp-apps/csp.js +93 -0
- package/dist/mcp-apps/csp.js.map +1 -0
- package/dist/mcp-apps/types.d.ts +1 -0
- package/dist/mcp-apps/types.d.ts.map +1 -1
- package/dist/mcp-apps/types.js.map +1 -1
- package/dist/model-context/makeAssistantVisible.d.ts +1 -0
- package/dist/model-context/makeAssistantVisible.d.ts.map +1 -1
- package/dist/model-context/makeAssistantVisible.js +31 -12
- package/dist/model-context/makeAssistantVisible.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.d.ts +1 -0
- package/dist/primitives/assistantModal/AssistantModalRoot.d.ts.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
- package/dist/primitives/attachment.d.ts +3 -1
- package/dist/primitives/attachment.d.ts.map +1 -1
- package/dist/primitives/attachment.js.map +1 -1
- package/dist/primitives/composer/ComposerCancel.d.ts +5 -1
- package/dist/primitives/composer/ComposerCancel.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerCancel.js +14 -1
- package/dist/primitives/composer/ComposerCancel.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.d.ts +15 -1
- package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +5 -4
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerRoot.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerRoot.js +24 -19
- package/dist/primitives/composer/ComposerRoot.js.map +1 -1
- package/dist/primitives/composer/composerInputSelector.d.ts +3 -0
- package/dist/primitives/composer/composerInputSelector.d.ts.map +1 -0
- package/dist/primitives/composer/composerInputSelector.js +7 -0
- package/dist/primitives/composer/composerInputSelector.js.map +1 -0
- package/dist/primitives/composer/trigger/TriggerPopover.d.ts +11 -11
- package/dist/primitives/composer/trigger/TriggerPopover.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.js +62 -60
- package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverAction.d.ts +8 -8
- package/dist/primitives/composer/trigger/TriggerPopoverAction.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverAction.js +6 -6
- package/dist/primitives/composer/trigger/TriggerPopoverAction.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.d.ts +3 -3
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverDirective.d.ts +9 -9
- package/dist/primitives/composer/trigger/TriggerPopoverDirective.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverDirective.js +7 -7
- package/dist/primitives/composer/trigger/TriggerPopoverDirective.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverItems.d.ts +5 -5
- package/dist/primitives/composer/trigger/TriggerPopoverItems.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverItems.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts +8 -8
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js +8 -8
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js.map +1 -1
- package/dist/primitives/composer/trigger/index.d.ts +1 -1
- package/dist/primitives/composer/useComposerCancelWithFocus.d.ts +2 -0
- package/dist/primitives/composer/useComposerCancelWithFocus.d.ts.map +1 -0
- package/dist/primitives/composer/useComposerCancelWithFocus.js +42 -0
- package/dist/primitives/composer/useComposerCancelWithFocus.js.map +1 -0
- package/dist/primitives/composer.d.ts +55 -15
- package/dist/primitives/composer.d.ts.map +1 -1
- package/dist/primitives/composer.js +8 -1
- package/dist/primitives/composer.js.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.d.ts +8 -1
- package/dist/primitives/message/MessagePartsGrouped.d.ts.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.js +58 -278
- package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.d.ts.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +5 -2
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
- package/dist/primitives/thread/ThreadLoadEarlier.d.ts +19 -0
- package/dist/primitives/thread/ThreadLoadEarlier.d.ts.map +1 -0
- package/dist/primitives/thread/ThreadLoadEarlier.js +36 -0
- package/dist/primitives/thread/ThreadLoadEarlier.js.map +1 -0
- package/dist/primitives/thread/ThreadMessages.d.ts +1 -1
- package/dist/primitives/thread/ThreadMessages.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadMessages.js +2 -2
- package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewport.js +4 -3
- package/dist/primitives/thread/ThreadViewport.js.map +1 -1
- package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js +2 -2
- package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +195 -17
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- 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/sandbox-host/SandboxHost.d.ts.map +1 -1
- package/dist/sandbox-host/SandboxHost.js +9 -3
- package/dist/sandbox-host/SandboxHost.js.map +1 -1
- package/dist/unstable/useComposerInput.d.ts +7 -4
- package/dist/unstable/useComposerInput.d.ts.map +1 -1
- package/dist/unstable/useComposerInput.js +4 -4
- package/dist/unstable/useComposerInput.js.map +1 -1
- package/dist/unstable/useComposerInputHistory.d.ts +3 -2
- package/dist/unstable/useComposerInputHistory.d.ts.map +1 -1
- package/dist/unstable/useComposerInputHistory.js +2 -2
- package/dist/unstable/useComposerInputHistory.js.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.d.ts +3 -2
- package/dist/unstable/useLiveCompletionAdapter.d.ts.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.js +8 -7
- package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
- package/dist/unstable/useMentionAdapter.d.ts +12 -3
- package/dist/unstable/useMentionAdapter.d.ts.map +1 -1
- package/dist/unstable/useMentionAdapter.js +2 -2
- package/dist/unstable/useMentionAdapter.js.map +1 -1
- package/dist/unstable/useMessageStallDetection.d.ts +4 -2
- package/dist/unstable/useMessageStallDetection.d.ts.map +1 -1
- package/dist/unstable/useMessageStallDetection.js +12 -8
- package/dist/unstable/useMessageStallDetection.js.map +1 -1
- package/dist/unstable/useSlashCommandAdapter.d.ts +5 -2
- package/dist/unstable/useSlashCommandAdapter.d.ts.map +1 -1
- package/dist/unstable/useSlashCommandAdapter.js +5 -4
- package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
- package/dist/unstable/webmcp/useWebMcpProvider.d.ts +4 -0
- package/dist/unstable/webmcp/useWebMcpProvider.d.ts.map +1 -1
- package/dist/unstable/webmcp/useWebMcpProvider.js +2 -0
- package/dist/unstable/webmcp/useWebMcpProvider.js.map +1 -1
- package/dist/utils/getSelectionMessageId.d.ts.map +1 -1
- package/dist/utils/getSelectionMessageId.js +31 -8
- package/dist/utils/getSelectionMessageId.js.map +1 -1
- package/dist/utils/hooks/useOnResizeContent.d.ts.map +1 -1
- package/dist/utils/hooks/useOnResizeContent.js +2 -22
- package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
- package/dist/utils/smooth/useSmooth.js +6 -6
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/dist/utils/useIsomorphicLayoutEffect.d.ts +3 -0
- package/dist/utils/useIsomorphicLayoutEffect.d.ts.map +1 -0
- package/dist/utils/useIsomorphicLayoutEffect.js +6 -0
- package/dist/utils/useIsomorphicLayoutEffect.js.map +1 -0
- package/package.json +12 -11
- package/src/hooks/useToolCallElapsed.test.tsx +37 -0
- package/src/hooks/useToolCallElapsed.ts +10 -7
- package/src/index.ts +47 -5
- package/src/mcp-apps/McpAppRenderer.tsx +3 -9
- package/src/mcp-apps/app-frame.test.tsx +38 -0
- package/src/mcp-apps/app-frame.tsx +5 -8
- package/src/mcp-apps/csp.test.ts +112 -0
- package/src/mcp-apps/csp.ts +124 -0
- package/src/mcp-apps/types.ts +1 -0
- package/src/model-context/frame/AssistantFrame.test.ts +45 -0
- package/src/model-context/frame/SPEC_AssistantFrame.md +1 -0
- package/src/model-context/makeAssistantVisible.test.tsx +315 -5
- package/src/model-context/makeAssistantVisible.tsx +43 -7
- package/src/primitives/assistantModal/AssistantModalRoot.tsx +1 -0
- package/src/primitives/attachment.ts +4 -1
- package/src/primitives/composer/ComposerCancel.focus.test.tsx +307 -0
- package/src/primitives/composer/ComposerCancel.ts +8 -1
- package/src/primitives/composer/ComposerInput.tsx +20 -2
- package/src/primitives/composer/ComposerRoot.tsx +6 -5
- package/src/primitives/composer/composerInputSelector.ts +4 -0
- package/src/primitives/composer/trigger/TriggerPopover.tsx +11 -11
- package/src/primitives/composer/trigger/TriggerPopoverAction.tsx +10 -13
- package/src/primitives/composer/trigger/TriggerPopoverCategories.tsx +2 -2
- package/src/primitives/composer/trigger/TriggerPopoverDirective.tsx +11 -14
- package/src/primitives/composer/trigger/TriggerPopoverExports.test.ts +61 -0
- package/src/primitives/composer/trigger/TriggerPopoverItems.tsx +3 -3
- package/src/primitives/composer/trigger/TriggerPopoverRootContext.tsx +8 -8
- package/src/primitives/composer/useComposerCancelWithFocus.ts +57 -0
- package/src/primitives/composer.ts +70 -15
- package/src/primitives/message/MessagePartsGrouped.test.tsx +106 -2
- package/src/primitives/message/MessagePartsGrouped.tsx +36 -134
- package/src/primitives/message/MessageRoot.test.tsx +125 -2
- package/src/primitives/selectionToolbar/SelectionToolbarRoot.test.tsx +20 -12
- package/src/primitives/selectionToolbar/SelectionToolbarRoot.tsx +6 -2
- package/src/primitives/thread/ThreadLoadEarlier.tsx +35 -0
- package/src/primitives/thread/ThreadMessages.tsx +1 -0
- package/src/primitives/thread/ThreadViewport.tsx +3 -3
- package/src/primitives/thread/topAnchor/useTopAnchorReserve.ts +2 -2
- package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +922 -68
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +330 -56
- package/src/primitives/thread.ts +2 -0
- package/src/sandbox-host/SandboxHost.test.tsx +34 -0
- package/src/sandbox-host/SandboxHost.tsx +11 -12
- package/src/tests/toolCallTiming.test.tsx +19 -2
- package/src/unstable/useComposerInput.ts +7 -4
- package/src/unstable/useComposerInputHistory.ts +3 -2
- package/src/unstable/useLiveCompletionAdapter.ts +9 -14
- package/src/unstable/useMentionAdapter.ts +12 -3
- package/src/unstable/useMessageStallDetection.test.tsx +40 -0
- package/src/unstable/useMessageStallDetection.ts +14 -8
- package/src/unstable/useSlashCommandAdapter.ts +8 -4
- package/src/unstable/webmcp/useWebMcpProvider.ts +4 -0
- package/src/utils/getSelectionMessageId.test.ts +231 -0
- package/src/utils/getSelectionMessageId.ts +45 -7
- package/src/utils/hooks/useOnResizeContent.ts +2 -29
- package/src/utils/smooth/useSmooth.test.tsx +16 -5
- package/src/utils/smooth/useSmooth.ts +7 -7
- package/src/utils/useIsomorphicLayoutEffect.test.tsx +21 -0
- package/src/utils/useIsomorphicLayoutEffect.ts +9 -0
|
@@ -2,14 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
import { act, render } from "@testing-library/react";
|
|
4
4
|
import { createContext, useContext } from "react";
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
import { hydrateRoot } from "react-dom/client";
|
|
7
|
+
import { renderToString } from "react-dom/server";
|
|
5
8
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
ChatModelAdapter,
|
|
11
|
+
ThreadMessageLike,
|
|
12
|
+
ThreadMessage,
|
|
13
|
+
} from "@assistant-ui/core";
|
|
7
14
|
import {
|
|
8
15
|
AssistantRuntimeProvider,
|
|
16
|
+
ThreadListItemRuntimeProvider,
|
|
9
17
|
useExternalStoreRuntime,
|
|
18
|
+
useLocalRuntime,
|
|
10
19
|
} from "@assistant-ui/core/react";
|
|
11
20
|
import { useAuiState } from "@assistant-ui/store";
|
|
12
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
ThreadPrimitiveMessageByIndex,
|
|
23
|
+
ThreadPrimitiveMessages,
|
|
24
|
+
} from "../thread/ThreadMessages";
|
|
13
25
|
import { ThreadPrimitiveRoot } from "../thread/ThreadRoot";
|
|
14
26
|
import { ThreadPrimitiveViewport } from "../thread/ThreadViewport";
|
|
15
27
|
import { MessagePrimitiveRoot } from "./MessageRoot";
|
|
@@ -22,6 +34,14 @@ const messages: ThreadMessageLike[] = [
|
|
|
22
34
|
},
|
|
23
35
|
];
|
|
24
36
|
|
|
37
|
+
const initialMessages: readonly ThreadMessageLike[] = [
|
|
38
|
+
{ role: "user", content: "Hydrated message" },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const chatModel: ChatModelAdapter = {
|
|
42
|
+
run: async () => ({ content: [] }),
|
|
43
|
+
};
|
|
44
|
+
|
|
25
45
|
class TestResizeObserver {
|
|
26
46
|
observe() {}
|
|
27
47
|
unobserve() {}
|
|
@@ -62,6 +82,64 @@ const Example = ({ visible = true }: { visible?: boolean }) => {
|
|
|
62
82
|
);
|
|
63
83
|
};
|
|
64
84
|
|
|
85
|
+
const HydrationMessage = () => (
|
|
86
|
+
<MessagePrimitiveRoot data-testid="hydration-message" />
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const LocalThread = ({
|
|
90
|
+
onRuntime,
|
|
91
|
+
turnAnchor,
|
|
92
|
+
}: {
|
|
93
|
+
onRuntime: (runtime: ReturnType<typeof useLocalRuntime>) => void;
|
|
94
|
+
turnAnchor: "bottom" | "top";
|
|
95
|
+
}) => {
|
|
96
|
+
const runtime = useLocalRuntime(chatModel, { initialMessages });
|
|
97
|
+
onRuntime(runtime);
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
101
|
+
<ThreadPrimitiveRoot>
|
|
102
|
+
<ThreadPrimitiveViewport
|
|
103
|
+
scrollToBottomOnInitialize={false}
|
|
104
|
+
turnAnchor={turnAnchor}
|
|
105
|
+
>
|
|
106
|
+
<ThreadPrimitiveMessages components={{ Message: HydrationMessage }} />
|
|
107
|
+
</ThreadPrimitiveViewport>
|
|
108
|
+
</ThreadPrimitiveRoot>
|
|
109
|
+
</AssistantRuntimeProvider>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const NestedLocalRuntime = ({
|
|
114
|
+
onRuntime,
|
|
115
|
+
turnAnchor,
|
|
116
|
+
}: {
|
|
117
|
+
onRuntime: (runtime: ReturnType<typeof useLocalRuntime>) => void;
|
|
118
|
+
turnAnchor: "bottom" | "top";
|
|
119
|
+
}) => {
|
|
120
|
+
const host = useExternalStoreRuntime<ThreadMessage>({
|
|
121
|
+
messages: [],
|
|
122
|
+
onNew: async () => {},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<AssistantRuntimeProvider runtime={host}>
|
|
127
|
+
<ThreadListItemRuntimeProvider runtime={host.threads.mainItem}>
|
|
128
|
+
<LocalThread onRuntime={onRuntime} turnAnchor={turnAnchor} />
|
|
129
|
+
</ThreadListItemRuntimeProvider>
|
|
130
|
+
</AssistantRuntimeProvider>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const renderOnServer = (node: ReactNode) => {
|
|
135
|
+
vi.stubGlobal("document", undefined);
|
|
136
|
+
try {
|
|
137
|
+
return renderToString(node);
|
|
138
|
+
} finally {
|
|
139
|
+
vi.unstubAllGlobals();
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
65
143
|
beforeEach(() => {
|
|
66
144
|
vi.stubGlobal("ResizeObserver", TestResizeObserver);
|
|
67
145
|
});
|
|
@@ -82,6 +160,51 @@ const mockHoveredElement = () => {
|
|
|
82
160
|
};
|
|
83
161
|
|
|
84
162
|
describe("MessagePrimitiveRoot", () => {
|
|
163
|
+
it.each(["bottom", "top"] as const)(
|
|
164
|
+
"hydrates nested local message ids with %s anchoring",
|
|
165
|
+
async (turnAnchor) => {
|
|
166
|
+
let serverRuntime: ReturnType<typeof useLocalRuntime> | undefined;
|
|
167
|
+
const html = renderOnServer(
|
|
168
|
+
<NestedLocalRuntime
|
|
169
|
+
onRuntime={(runtime) => (serverRuntime = runtime)}
|
|
170
|
+
turnAnchor={turnAnchor}
|
|
171
|
+
/>,
|
|
172
|
+
);
|
|
173
|
+
const serverMessageId = serverRuntime!.thread.getState().messages[0]!.id;
|
|
174
|
+
const container = document.createElement("div");
|
|
175
|
+
container.innerHTML = html;
|
|
176
|
+
vi.stubGlobal("ResizeObserver", TestResizeObserver);
|
|
177
|
+
|
|
178
|
+
let clientRuntime: ReturnType<typeof useLocalRuntime> | undefined;
|
|
179
|
+
const errors = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
180
|
+
let root: ReturnType<typeof hydrateRoot> | undefined;
|
|
181
|
+
await act(async () => {
|
|
182
|
+
root = hydrateRoot(
|
|
183
|
+
container,
|
|
184
|
+
<NestedLocalRuntime
|
|
185
|
+
onRuntime={(runtime) => (clientRuntime = runtime)}
|
|
186
|
+
turnAnchor={turnAnchor}
|
|
187
|
+
/>,
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const clientMessageId = clientRuntime!.thread.getState().messages[0]!.id;
|
|
192
|
+
const domMessageId = container
|
|
193
|
+
.querySelector("[data-testid='hydration-message']")!
|
|
194
|
+
.getAttribute("data-message-id");
|
|
195
|
+
|
|
196
|
+
expect(clientMessageId).toBe(serverMessageId);
|
|
197
|
+
expect(domMessageId).toBe(clientMessageId);
|
|
198
|
+
expect(
|
|
199
|
+
errors.mock.calls.some((args) =>
|
|
200
|
+
args.some((value) => String(value).includes("data-message-id")),
|
|
201
|
+
),
|
|
202
|
+
).toBe(false);
|
|
203
|
+
|
|
204
|
+
await act(async () => root?.unmount());
|
|
205
|
+
},
|
|
206
|
+
);
|
|
207
|
+
|
|
85
208
|
it("synchronizes hover state while mounted", async () => {
|
|
86
209
|
mockHoveredElement();
|
|
87
210
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @vitest-environment jsdom */
|
|
2
2
|
import type { MouseEvent, ReactNode } from "react";
|
|
3
|
-
import { fireEvent, render } from "@testing-library/react";
|
|
3
|
+
import { act, fireEvent, render } from "@testing-library/react";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
import type * as StoreModule from "@assistant-ui/store";
|
|
6
6
|
import { ThreadPrimitiveRoot } from "../thread/ThreadRoot";
|
|
@@ -96,14 +96,6 @@ describe("SelectionToolbarPrimitiveRoot onMouseDown composition", () => {
|
|
|
96
96
|
});
|
|
97
97
|
|
|
98
98
|
describe("SelectionToolbarPrimitiveRoot selection changes", () => {
|
|
99
|
-
it("opens from a selectionchange event without a mouse or key event", () => {
|
|
100
|
-
render(<SelectionToolbarPrimitiveRoot data-testid="toolbar" />);
|
|
101
|
-
|
|
102
|
-
fireEvent(document, new Event("selectionchange"));
|
|
103
|
-
|
|
104
|
-
expect(document.querySelector('[data-testid="toolbar"]')).not.toBeNull();
|
|
105
|
-
});
|
|
106
|
-
|
|
107
99
|
it("closes an open toolbar when the selection collapses", () => {
|
|
108
100
|
render(<SelectionToolbarPrimitiveRoot data-testid="toolbar" />);
|
|
109
101
|
fireEvent(document, new Event("selectionchange"));
|
|
@@ -282,17 +274,33 @@ describe("SelectionToolbarPrimitiveRoot frame cleanup", () => {
|
|
|
282
274
|
expect(frames).toHaveLength(2);
|
|
283
275
|
});
|
|
284
276
|
|
|
285
|
-
it("
|
|
277
|
+
it("shows the toolbar when the window blurs during a drag", () => {
|
|
286
278
|
const { frames } = deferFrames();
|
|
287
|
-
render(<SelectionToolbarPrimitiveRoot />);
|
|
279
|
+
render(<SelectionToolbarPrimitiveRoot data-testid="toolbar" />);
|
|
288
280
|
|
|
289
281
|
fireEvent.mouseDown(document);
|
|
290
282
|
fireEvent(document, new Event("selectionchange"));
|
|
291
283
|
expect(frames).toHaveLength(0);
|
|
292
284
|
|
|
293
285
|
fireEvent.blur(window);
|
|
294
|
-
fireEvent(document, new Event("selectionchange"));
|
|
295
286
|
expect(frames).toHaveLength(1);
|
|
287
|
+
|
|
288
|
+
act(() => frames[0]?.(0));
|
|
289
|
+
|
|
290
|
+
expect(document.querySelector('[data-testid="toolbar"]')).not.toBeNull();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("does not reopen a toolbar hidden by scroll on an unrelated window blur", () => {
|
|
294
|
+
render(<SelectionToolbarPrimitiveRoot data-testid="toolbar" />);
|
|
295
|
+
|
|
296
|
+
fireEvent(document, new Event("selectionchange"));
|
|
297
|
+
expect(document.querySelector('[data-testid="toolbar"]')).not.toBeNull();
|
|
298
|
+
|
|
299
|
+
fireEvent.scroll(document);
|
|
300
|
+
expect(document.querySelector('[data-testid="toolbar"]')).toBeNull();
|
|
301
|
+
|
|
302
|
+
fireEvent.blur(window);
|
|
303
|
+
expect(document.querySelector('[data-testid="toolbar"]')).toBeNull();
|
|
296
304
|
});
|
|
297
305
|
|
|
298
306
|
it("recovers when a context menu consumes the mouseup of a press", () => {
|
|
@@ -134,12 +134,16 @@ export const SelectionToolbarPrimitiveRoot = forwardRef<
|
|
|
134
134
|
isMouseDragging = false;
|
|
135
135
|
};
|
|
136
136
|
|
|
137
|
+
const handleWindowBlur = () => {
|
|
138
|
+
if (isMouseDragging) handleMouseUp();
|
|
139
|
+
};
|
|
140
|
+
|
|
137
141
|
document.addEventListener("mousedown", handleMouseDown, true);
|
|
138
142
|
document.addEventListener("mouseup", handleMouseUp, true);
|
|
139
143
|
document.addEventListener("dragend", handleMouseUp, true);
|
|
140
144
|
// A context menu can consume the mouseup of the press that opened it.
|
|
141
145
|
document.addEventListener("contextmenu", handleMouseCancel, true);
|
|
142
|
-
window.addEventListener("blur",
|
|
146
|
+
window.addEventListener("blur", handleWindowBlur);
|
|
143
147
|
document.addEventListener("selectionchange", handleSelectionChange);
|
|
144
148
|
document.addEventListener("scroll", handleScroll, true);
|
|
145
149
|
|
|
@@ -149,7 +153,7 @@ export const SelectionToolbarPrimitiveRoot = forwardRef<
|
|
|
149
153
|
document.removeEventListener("mouseup", handleMouseUp, true);
|
|
150
154
|
document.removeEventListener("dragend", handleMouseUp, true);
|
|
151
155
|
document.removeEventListener("contextmenu", handleMouseCancel, true);
|
|
152
|
-
window.removeEventListener("blur",
|
|
156
|
+
window.removeEventListener("blur", handleWindowBlur);
|
|
153
157
|
document.removeEventListener("selectionchange", handleSelectionChange);
|
|
154
158
|
document.removeEventListener("scroll", handleScroll, true);
|
|
155
159
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback } from "react";
|
|
4
|
+
import { useAui, useAuiState } from "@assistant-ui/store";
|
|
5
|
+
import {
|
|
6
|
+
type ActionButtonElement,
|
|
7
|
+
type ActionButtonProps,
|
|
8
|
+
createActionButton,
|
|
9
|
+
} from "../../utils/createActionButton";
|
|
10
|
+
|
|
11
|
+
const useThreadLoadEarlier = () => {
|
|
12
|
+
const aui = useAui();
|
|
13
|
+
const hasEarlier = useAuiState((s) => s.thread.hasEarlier);
|
|
14
|
+
const loadEarlier = useCallback(() => {
|
|
15
|
+
void aui.thread.loadEarlier();
|
|
16
|
+
}, [aui]);
|
|
17
|
+
if (!hasEarlier) return null;
|
|
18
|
+
return loadEarlier;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export namespace ThreadPrimitiveLoadEarlier {
|
|
22
|
+
export type Element = ActionButtonElement;
|
|
23
|
+
export type Props = ActionButtonProps<typeof useThreadLoadEarlier>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A button that loads the page before the first loaded message. Disabled
|
|
28
|
+
* while the runtime reports no earlier messages. A press while a page is
|
|
29
|
+
* loading joins that load, so the button stays enabled and keeps keyboard
|
|
30
|
+
* focus until the page lands; read `thread.isLoadingEarlier` to show progress.
|
|
31
|
+
*/
|
|
32
|
+
export const ThreadPrimitiveLoadEarlier = createActionButton(
|
|
33
|
+
"ThreadPrimitive.LoadEarlier",
|
|
34
|
+
useThreadLoadEarlier,
|
|
35
|
+
);
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
forwardRef,
|
|
8
8
|
type ComponentPropsWithoutRef,
|
|
9
9
|
useCallback,
|
|
10
|
-
useLayoutEffect,
|
|
11
10
|
useMemo,
|
|
12
11
|
} from "react";
|
|
13
12
|
import { useAuiState } from "@assistant-ui/store";
|
|
@@ -20,6 +19,7 @@ import {
|
|
|
20
19
|
useThreadViewportStore,
|
|
21
20
|
} from "../../context/react/ThreadViewportContext";
|
|
22
21
|
import { useTopAnchorReserve } from "./topAnchor/useTopAnchorReserve";
|
|
22
|
+
import { useIsomorphicLayoutEffect } from "../../utils/useIsomorphicLayoutEffect";
|
|
23
23
|
import {
|
|
24
24
|
getActiveTopAnchorAnchorId,
|
|
25
25
|
getActiveTopAnchorTargetId,
|
|
@@ -124,13 +124,13 @@ const useTopAnchorTurn = (enabled: boolean) => {
|
|
|
124
124
|
isTopAnchorTurnValid(topAnchorTurn, s.thread.messages),
|
|
125
125
|
);
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
useIsomorphicLayoutEffect(() => {
|
|
128
128
|
if (!topAnchorTurn || topAnchorTurnIsValid) return;
|
|
129
129
|
|
|
130
130
|
threadViewportStore.getState().setTopAnchorTurn(null);
|
|
131
131
|
}, [threadViewportStore, topAnchorTurn, topAnchorTurnIsValid]);
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
useIsomorphicLayoutEffect(() => {
|
|
134
134
|
if (!activeTurn) return;
|
|
135
135
|
|
|
136
136
|
const state = threadViewportStore.getState();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useIsomorphicLayoutEffect } from "../../../utils/useIsomorphicLayoutEffect";
|
|
4
4
|
import { useThreadViewportStore } from "../../../context/react/ThreadViewportContext";
|
|
5
5
|
import { mountTopAnchorReserve } from "./mountTopAnchorReserve";
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ import { mountTopAnchorReserve } from "./mountTopAnchorReserve";
|
|
|
12
12
|
export const useTopAnchorReserve = (enabled: boolean) => {
|
|
13
13
|
const threadViewportStore = useThreadViewportStore();
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
useIsomorphicLayoutEffect(() => {
|
|
16
16
|
if (!enabled) return;
|
|
17
17
|
return mountTopAnchorReserve(threadViewportStore);
|
|
18
18
|
}, [enabled, threadViewportStore]);
|