@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
|
@@ -43,10 +43,37 @@ const findQuoteMarker = (
|
|
|
43
43
|
return marker;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
const normalizeRangeEnd = (range: Range): Range => {
|
|
47
|
+
if (range.endOffset !== 0 || range.collapsed) return range;
|
|
48
|
+
|
|
49
|
+
const walker = range.endContainer.ownerDocument?.createTreeWalker(
|
|
50
|
+
range.commonAncestorContainer,
|
|
51
|
+
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
|
|
49
52
|
);
|
|
53
|
+
if (!walker) return range;
|
|
54
|
+
walker.currentNode = range.endContainer;
|
|
55
|
+
|
|
56
|
+
while (walker.previousNode()) {
|
|
57
|
+
const node = walker.currentNode;
|
|
58
|
+
const offset = node instanceof Text ? node.length : node.childNodes.length;
|
|
59
|
+
if (range.comparePoint(node, offset) < 0) return range;
|
|
60
|
+
const element = node instanceof Element ? node : node.parentElement;
|
|
61
|
+
const marker = element?.closest(QUOTE_SELECTABLE_SELECTOR);
|
|
62
|
+
if (marker && isExcluded(marker) && !node.contains(range.endContainer))
|
|
63
|
+
return range;
|
|
64
|
+
if (!(node instanceof Text) || !node.data.trim()) continue;
|
|
65
|
+
|
|
66
|
+
const normalized = range.cloneRange();
|
|
67
|
+
normalized.setEnd(node, offset);
|
|
68
|
+
return normalized.collapsed ? range : normalized;
|
|
69
|
+
}
|
|
70
|
+
return range;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const intersectsExcluded = (
|
|
74
|
+
scope: Element,
|
|
75
|
+
ranges: readonly Range[],
|
|
76
|
+
): boolean => {
|
|
50
77
|
for (const marker of scope.querySelectorAll(QUOTE_SELECTABLE_SELECTOR)) {
|
|
51
78
|
if (!isExcluded(marker)) continue;
|
|
52
79
|
if (ranges.some((range) => range.intersectsNode(marker))) return true;
|
|
@@ -58,9 +85,21 @@ export const getSelectionMessageId = (
|
|
|
58
85
|
selection: Selection,
|
|
59
86
|
root?: Element | null,
|
|
60
87
|
): string | null => {
|
|
61
|
-
|
|
88
|
+
let { anchorNode, focusNode } = selection;
|
|
62
89
|
if (!anchorNode || !focusNode) return null;
|
|
63
90
|
|
|
91
|
+
const ranges = Array.from({ length: selection.rangeCount }, (_, i) =>
|
|
92
|
+
selection.getRangeAt(i),
|
|
93
|
+
);
|
|
94
|
+
if (ranges.length === 1) {
|
|
95
|
+
const range = normalizeRangeEnd(ranges[0]!);
|
|
96
|
+
if (range !== ranges[0]) {
|
|
97
|
+
ranges[0] = range;
|
|
98
|
+
anchorNode = range.startContainer;
|
|
99
|
+
focusNode = range.endContainer;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
64
103
|
const anchorMessageElement = findMessageElement(anchorNode);
|
|
65
104
|
const focusMessageElement = findMessageElement(focusNode);
|
|
66
105
|
|
|
@@ -86,10 +125,9 @@ export const getSelectionMessageId = (
|
|
|
86
125
|
|
|
87
126
|
const scope = anchorMarker ?? anchorMessageElement;
|
|
88
127
|
|
|
89
|
-
for (
|
|
90
|
-
const { commonAncestorContainer } = selection.getRangeAt(i);
|
|
128
|
+
for (const { commonAncestorContainer } of ranges) {
|
|
91
129
|
if (!scope.contains(commonAncestorContainer)) return null;
|
|
92
130
|
}
|
|
93
131
|
|
|
94
|
-
return intersectsExcluded(scope,
|
|
132
|
+
return intersectsExcluded(scope, ranges) ? null : messageId;
|
|
95
133
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { observeContentResize } from "@assistant-ui/store/client";
|
|
1
2
|
import { useCallbackRef } from "radix-ui/internal";
|
|
2
3
|
import { useCallback } from "react";
|
|
3
4
|
import { useManagedRef } from "./useManagedRef";
|
|
@@ -6,35 +7,7 @@ export const useOnResizeContent = (callback: () => void) => {
|
|
|
6
7
|
const callbackRef = useCallbackRef(callback);
|
|
7
8
|
|
|
8
9
|
const refCallback = useCallback(
|
|
9
|
-
(el: HTMLElement) =>
|
|
10
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
11
|
-
callbackRef();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
const mutationObserver = new MutationObserver((mutations) => {
|
|
15
|
-
// Filter out style-only attribute mutations to prevent feedback loops
|
|
16
|
-
// with code paths that write styles in response to viewport changes.
|
|
17
|
-
const hasRelevantMutation = mutations.some(
|
|
18
|
-
(m) => m.type !== "attributes" || m.attributeName !== "style",
|
|
19
|
-
);
|
|
20
|
-
if (hasRelevantMutation) {
|
|
21
|
-
callbackRef();
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
resizeObserver.observe(el);
|
|
26
|
-
mutationObserver.observe(el, {
|
|
27
|
-
childList: true,
|
|
28
|
-
subtree: true,
|
|
29
|
-
attributes: true,
|
|
30
|
-
characterData: true,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
return () => {
|
|
34
|
-
resizeObserver.disconnect();
|
|
35
|
-
mutationObserver.disconnect();
|
|
36
|
-
};
|
|
37
|
-
},
|
|
10
|
+
(el: HTMLElement) => observeContentResize(el, callbackRef),
|
|
38
11
|
[callbackRef],
|
|
39
12
|
);
|
|
40
13
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** @vitest-environment jsdom */
|
|
2
2
|
import { act, renderHook } from "@testing-library/react";
|
|
3
|
+
import { renderToString } from "react-dom/server";
|
|
3
4
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
5
|
import type {
|
|
5
6
|
MessagePartState,
|
|
@@ -68,7 +69,7 @@ const driveAndCount = (minCommitMs: number) => {
|
|
|
68
69
|
.spyOn(globalThis, "cancelAnimationFrame")
|
|
69
70
|
.mockImplementation(() => {});
|
|
70
71
|
let now = 1000;
|
|
71
|
-
const nowSpy = vi.spyOn(
|
|
72
|
+
const nowSpy = vi.spyOn(performance, "now").mockImplementation(() => now);
|
|
72
73
|
|
|
73
74
|
const state = runningState("0123456789");
|
|
74
75
|
const { result, unmount } = renderHook(() =>
|
|
@@ -105,6 +106,16 @@ describe("useSmooth", () => {
|
|
|
105
106
|
Reflect.deleteProperty(window, "matchMedia");
|
|
106
107
|
});
|
|
107
108
|
|
|
109
|
+
it("does not read the wall clock during server render", () => {
|
|
110
|
+
const nowSpy = vi.spyOn(Date, "now");
|
|
111
|
+
const Probe = () => (
|
|
112
|
+
<span>{useSmooth(runningState("streaming"), true).text}</span>
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
expect(renderToString(<Probe />)).toBe("<span></span>");
|
|
116
|
+
expect(nowSpy).not.toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
|
|
108
119
|
it("returns the input state unchanged when disabled", () => {
|
|
109
120
|
const state = textState("hello");
|
|
110
121
|
const { result } = renderHook(() => useSmooth(state, false));
|
|
@@ -189,8 +200,8 @@ describe("useSmooth", () => {
|
|
|
189
200
|
return raf.length;
|
|
190
201
|
});
|
|
191
202
|
vi.spyOn(globalThis, "cancelAnimationFrame").mockImplementation(() => {});
|
|
192
|
-
let now =
|
|
193
|
-
vi.spyOn(
|
|
203
|
+
let now = 0;
|
|
204
|
+
vi.spyOn(performance, "now").mockImplementation(() => now);
|
|
194
205
|
|
|
195
206
|
const frame = () => {
|
|
196
207
|
if (raf.length === 0) return;
|
|
@@ -246,7 +257,7 @@ describe("useSmooth", () => {
|
|
|
246
257
|
});
|
|
247
258
|
vi.spyOn(globalThis, "cancelAnimationFrame").mockImplementation(() => {});
|
|
248
259
|
let now = 1000;
|
|
249
|
-
vi.spyOn(
|
|
260
|
+
vi.spyOn(performance, "now").mockImplementation(() => now);
|
|
250
261
|
|
|
251
262
|
const frame = () => {
|
|
252
263
|
if (raf.length === 0) return;
|
|
@@ -297,7 +308,7 @@ describe("useSmooth", () => {
|
|
|
297
308
|
});
|
|
298
309
|
vi.spyOn(globalThis, "cancelAnimationFrame").mockImplementation(() => {});
|
|
299
310
|
let now = 1000;
|
|
300
|
-
vi.spyOn(
|
|
311
|
+
vi.spyOn(performance, "now").mockImplementation(() => now);
|
|
301
312
|
|
|
302
313
|
const frame = () => {
|
|
303
314
|
if (raf.length === 0) return;
|
|
@@ -49,8 +49,8 @@ const DEFAULT_MAX_CHAR_INTERVAL_MS = 5;
|
|
|
49
49
|
|
|
50
50
|
class TextStreamAnimator {
|
|
51
51
|
private animationFrameId: number | null = null;
|
|
52
|
-
private lastUpdateTime: number =
|
|
53
|
-
public lastCommitTime: number =
|
|
52
|
+
private lastUpdateTime: number = 0;
|
|
53
|
+
public lastCommitTime: number = -Infinity;
|
|
54
54
|
|
|
55
55
|
public targetText: string = "";
|
|
56
56
|
public drainMs: number = DEFAULT_DRAIN_MS;
|
|
@@ -68,7 +68,7 @@ class TextStreamAnimator {
|
|
|
68
68
|
|
|
69
69
|
start() {
|
|
70
70
|
if (this.animationFrameId !== null) return;
|
|
71
|
-
this.lastUpdateTime =
|
|
71
|
+
this.lastUpdateTime = performance.now();
|
|
72
72
|
this.animate();
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -80,7 +80,7 @@ class TextStreamAnimator {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
private animate = () => {
|
|
83
|
-
const currentTime =
|
|
83
|
+
const currentTime = performance.now();
|
|
84
84
|
const deltaTime = currentTime - this.lastUpdateTime;
|
|
85
85
|
let timeToConsume = deltaTime;
|
|
86
86
|
|
|
@@ -214,8 +214,8 @@ export const useSmooth = (
|
|
|
214
214
|
}
|
|
215
215
|
}, [smoothStatusStore, enabled, text, displayedText, state.status]);
|
|
216
216
|
|
|
217
|
-
const [animatorRef] = useState
|
|
218
|
-
new TextStreamAnimator(displayedText, setText),
|
|
217
|
+
const [animatorRef] = useState(
|
|
218
|
+
() => new TextStreamAnimator(displayedText, setText),
|
|
219
219
|
);
|
|
220
220
|
|
|
221
221
|
useEffect(() => {
|
|
@@ -240,7 +240,7 @@ export const useSmooth = (
|
|
|
240
240
|
if (state.status.type === "running") {
|
|
241
241
|
animatorRef.currentText = "";
|
|
242
242
|
animatorRef.targetText = text;
|
|
243
|
-
animatorRef.lastCommitTime =
|
|
243
|
+
animatorRef.lastCommitTime = -Infinity;
|
|
244
244
|
animatorRef.start();
|
|
245
245
|
} else {
|
|
246
246
|
animatorRef.currentText = text;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { renderToString } from "react-dom/server";
|
|
3
|
+
import { expect, it, vi } from "vitest";
|
|
4
|
+
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
|
|
5
|
+
|
|
6
|
+
it("does not warn about useLayoutEffect during server rendering", () => {
|
|
7
|
+
const errors = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
8
|
+
function Probe() {
|
|
9
|
+
useIsomorphicLayoutEffect(() => {});
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
renderToString(createElement(Probe));
|
|
15
|
+
expect(errors.mock.calls.flat().join(" ")).not.toMatch(
|
|
16
|
+
/useLayoutEffect does nothing on the server/,
|
|
17
|
+
);
|
|
18
|
+
} finally {
|
|
19
|
+
errors.mockRestore();
|
|
20
|
+
}
|
|
21
|
+
});
|