@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
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
useTriggerPopoverAriaProps,
|
|
13
13
|
} from "../primitives/composer/useComposerInputState";
|
|
14
14
|
|
|
15
|
+
/** @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release. */
|
|
15
16
|
export type Unstable_UseComposerInputOptions = {
|
|
16
17
|
/**
|
|
17
18
|
* Disables the input in addition to the composer's own disabled sources
|
|
@@ -21,6 +22,7 @@ export type Unstable_UseComposerInputOptions = {
|
|
|
21
22
|
disabled?: boolean | undefined;
|
|
22
23
|
};
|
|
23
24
|
|
|
25
|
+
/** @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release. */
|
|
24
26
|
export type Unstable_ComposerInput = {
|
|
25
27
|
/** Current composer text, or `""` when the composer is not editing. */
|
|
26
28
|
value: string;
|
|
@@ -49,8 +51,6 @@ export type Unstable_ComposerInput = {
|
|
|
49
51
|
};
|
|
50
52
|
|
|
51
53
|
/**
|
|
52
|
-
* @deprecated Under active development and might change without notice.
|
|
53
|
-
*
|
|
54
54
|
* Headless bridge to the composer's text value and send action, for building a
|
|
55
55
|
* custom composer input without `ComposerPrimitive.Input`. It is a thin bridge,
|
|
56
56
|
* not a second input: it does not own keyboard behavior, autosize, IME or
|
|
@@ -58,6 +58,8 @@ export type Unstable_ComposerInput = {
|
|
|
58
58
|
* state. Spread `unstable_useTriggerPopoverAriaProps()` onto your element for
|
|
59
59
|
* trigger-popover combobox semantics.
|
|
60
60
|
*
|
|
61
|
+
* @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release.
|
|
62
|
+
*
|
|
61
63
|
* @example
|
|
62
64
|
* ```tsx
|
|
63
65
|
* const { value, setText, send, isDisabled, canSend } = unstable_useComposerInput();
|
|
@@ -104,17 +106,18 @@ export function unstable_useComposerInput(
|
|
|
104
106
|
return { value, setText, send, isDisabled, canSend };
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
/** @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release. */
|
|
107
110
|
export type Unstable_TriggerPopoverAriaProps = TriggerPopoverAriaProps;
|
|
108
111
|
|
|
109
112
|
/**
|
|
110
|
-
* @deprecated Under active development and might change without notice.
|
|
111
|
-
*
|
|
112
113
|
* ARIA combobox attributes for the focused element (typically the composer
|
|
113
114
|
* input) describing the open trigger popover, per the WAI-ARIA editable
|
|
114
115
|
* combobox pattern. Returns an empty object outside a `TriggerPopoverRoot` or
|
|
115
116
|
* when no popover is open. Spread these last so they take precedence over any
|
|
116
117
|
* matching ARIA props you set yourself, mirroring `ComposerPrimitive.Input`.
|
|
117
118
|
*
|
|
119
|
+
* @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release.
|
|
120
|
+
*
|
|
118
121
|
* @example
|
|
119
122
|
* ```tsx
|
|
120
123
|
* const aria = unstable_useTriggerPopoverAriaProps();
|
|
@@ -14,6 +14,7 @@ import type { ThreadMessage } from "@assistant-ui/core";
|
|
|
14
14
|
import { getThreadMessageText } from "@assistant-ui/core/internal";
|
|
15
15
|
import { useTriggerPopoverRootContextOptional } from "../primitives/composer/trigger/TriggerPopoverRootContext";
|
|
16
16
|
|
|
17
|
+
/** @deprecated Experimental since 2026-06-11. Not scheduled for removal; the API may change in any release. */
|
|
17
18
|
export type Unstable_ComposerInputHistory = {
|
|
18
19
|
/** Keydown handler to spread onto `ComposerPrimitive.Input`. */
|
|
19
20
|
onKeyDown: KeyboardEventHandler<HTMLTextAreaElement>;
|
|
@@ -45,8 +46,6 @@ const isOnLastLine = (value: string, caret: number): boolean =>
|
|
|
45
46
|
!value.slice(caret).includes("\n");
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
|
-
* @deprecated Under active development and might change without notice.
|
|
49
|
-
*
|
|
50
49
|
* Terminal-style input history for the thread composer: ArrowUp on an
|
|
51
50
|
* empty draft recalls previously sent user messages (newest first),
|
|
52
51
|
* ArrowDown steps back toward the newest and finally restores the draft
|
|
@@ -58,6 +57,8 @@ const isOnLastLine = (value: string, caret: number): boolean =>
|
|
|
58
57
|
* to modifier keys, and to consumer handlers that already called
|
|
59
58
|
* `preventDefault`. It is inert on edit composers.
|
|
60
59
|
*
|
|
60
|
+
* @deprecated Experimental since 2026-06-11. Not scheduled for removal; the API may change in any release.
|
|
61
|
+
*
|
|
61
62
|
* @example
|
|
62
63
|
* ```tsx
|
|
63
64
|
* const history = unstable_useComposerInputHistory();
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
useEffect,
|
|
6
|
-
useLayoutEffect,
|
|
7
|
-
useMemo,
|
|
8
|
-
useRef,
|
|
9
|
-
useState,
|
|
10
|
-
} from "react";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { useIsomorphicLayoutEffect } from "../utils/useIsomorphicLayoutEffect";
|
|
11
5
|
import type {
|
|
12
6
|
Unstable_TriggerAdapter,
|
|
13
7
|
Unstable_TriggerItem,
|
|
14
8
|
} from "@assistant-ui/core";
|
|
15
9
|
|
|
10
|
+
/** @deprecated Experimental since 2026-06-15. Not scheduled for removal; the API may change in any release. */
|
|
16
11
|
export type Unstable_UseLiveCompletionAdapterOptions = {
|
|
17
12
|
/**
|
|
18
13
|
* Fetches the items for a query from an async source. Called debounced; the
|
|
@@ -35,8 +30,6 @@ export type Unstable_UseLiveCompletionAdapterOptions = {
|
|
|
35
30
|
const NO_QUERY = "\u0000";
|
|
36
31
|
|
|
37
32
|
/**
|
|
38
|
-
* @deprecated Under active development and may change without notice.
|
|
39
|
-
*
|
|
40
33
|
* Bridges an async completion source (a server search, a gateway RPC) into the
|
|
41
34
|
* synchronous `Unstable_TriggerAdapter` that `ComposerTriggerPopover` consumes.
|
|
42
35
|
* `search(query)` returns the last fetched items synchronously and schedules a
|
|
@@ -47,6 +40,8 @@ const NO_QUERY = "\u0000";
|
|
|
47
40
|
* `isLoading` is `true` while a fetch is in flight. Pass it to the popover's
|
|
48
41
|
* `isLoading` prop to render a loading state.
|
|
49
42
|
*
|
|
43
|
+
* @deprecated Experimental since 2026-06-15. Not scheduled for removal; the API may change in any release.
|
|
44
|
+
*
|
|
50
45
|
* @example
|
|
51
46
|
* ```tsx
|
|
52
47
|
* const mentions = unstable_useLiveCompletionAdapter({
|
|
@@ -75,7 +70,7 @@ export function unstable_useLiveCompletionAdapter(
|
|
|
75
70
|
|
|
76
71
|
const fetcherRef = useRef(fetcher);
|
|
77
72
|
// The debounce timer must only observe fetchers from committed renders.
|
|
78
|
-
|
|
73
|
+
useIsomorphicLayoutEffect(() => {
|
|
79
74
|
fetcherRef.current = fetcher;
|
|
80
75
|
}, [fetcher]);
|
|
81
76
|
|
|
@@ -143,7 +138,7 @@ export function unstable_useLiveCompletionAdapter(
|
|
|
143
138
|
);
|
|
144
139
|
|
|
145
140
|
const scheduleFetchRef = useRef(scheduleFetch);
|
|
146
|
-
|
|
141
|
+
useIsomorphicLayoutEffect(() => {
|
|
147
142
|
scheduleFetchRef.current = scheduleFetch;
|
|
148
143
|
}, [scheduleFetch]);
|
|
149
144
|
|
|
@@ -156,7 +151,7 @@ export function unstable_useLiveCompletionAdapter(
|
|
|
156
151
|
}, [cancelTimer, rearmPendingRetry]);
|
|
157
152
|
|
|
158
153
|
const cacheKeyRef = useRef(cacheKey);
|
|
159
|
-
|
|
154
|
+
useIsomorphicLayoutEffect(() => {
|
|
160
155
|
if (cacheKeyRef.current === cacheKey) return;
|
|
161
156
|
cacheKeyRef.current = cacheKey;
|
|
162
157
|
invalidatePending();
|
|
@@ -175,7 +170,7 @@ export function unstable_useLiveCompletionAdapter(
|
|
|
175
170
|
|
|
176
171
|
// Render-time searches can outlive an abandoned render, so they only arm
|
|
177
172
|
// request work after this hook commits.
|
|
178
|
-
|
|
173
|
+
useIsomorphicLayoutEffect(() => {
|
|
179
174
|
inactiveRef.current = false;
|
|
180
175
|
const deferredQuery = deferredQueryRef.current;
|
|
181
176
|
deferredQueryRef.current = null;
|
|
@@ -17,9 +17,14 @@ import {
|
|
|
17
17
|
type ModelContextSnapshotSource,
|
|
18
18
|
} from "./useModelContextSnapshot";
|
|
19
19
|
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Icon component shape consumed by `ComposerTriggerPopover`'s `iconMap`.
|
|
22
|
+
*
|
|
23
|
+
* @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release.
|
|
24
|
+
*/
|
|
21
25
|
export type Unstable_IconComponent = FC<{ className?: string }>;
|
|
22
26
|
|
|
27
|
+
/** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
|
|
23
28
|
export type Unstable_Mention = {
|
|
24
29
|
readonly id: string;
|
|
25
30
|
readonly type: string;
|
|
@@ -30,12 +35,14 @@ export type Unstable_Mention = {
|
|
|
30
35
|
readonly metadata?: ReadonlyJSONObject | undefined;
|
|
31
36
|
};
|
|
32
37
|
|
|
38
|
+
/** @deprecated Experimental since 2026-03-16. Not scheduled for removal; the API may change in any release. */
|
|
33
39
|
export type Unstable_MentionCategory = {
|
|
34
40
|
readonly id: string;
|
|
35
41
|
readonly label: string;
|
|
36
42
|
readonly items: readonly Unstable_Mention[];
|
|
37
43
|
};
|
|
38
44
|
|
|
45
|
+
/** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
|
|
39
46
|
export type Unstable_ModelContextToolsOptions = {
|
|
40
47
|
/**
|
|
41
48
|
* Wrap tools in a dedicated category. Selects drill-down mode on its own
|
|
@@ -48,6 +55,7 @@ export type Unstable_ModelContextToolsOptions = {
|
|
|
48
55
|
readonly icon?: string;
|
|
49
56
|
};
|
|
50
57
|
|
|
58
|
+
/** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
|
|
51
59
|
export type Unstable_UseMentionAdapterOptions = {
|
|
52
60
|
/**
|
|
53
61
|
* Flat mention list. Ignored when `categories` is set, and keeps the
|
|
@@ -78,6 +86,7 @@ export type Unstable_UseMentionAdapterOptions = {
|
|
|
78
86
|
readonly fallbackIcon?: Unstable_IconComponent;
|
|
79
87
|
};
|
|
80
88
|
|
|
89
|
+
/** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
|
|
81
90
|
export type Unstable_MentionDirective = {
|
|
82
91
|
readonly formatter: Unstable_DirectiveFormatter;
|
|
83
92
|
readonly onInserted?: ((item: Unstable_TriggerItem) => void) | undefined;
|
|
@@ -120,12 +129,12 @@ const toolMentionSource: ModelContextSnapshotSource<
|
|
|
120
129
|
};
|
|
121
130
|
|
|
122
131
|
/**
|
|
123
|
-
* @deprecated Under active development and might change without notice.
|
|
124
|
-
*
|
|
125
132
|
* Creates a spreadable `{ adapter, directive }` bundle for `@` mentions.
|
|
126
133
|
* Supports tools registered in model context, explicit items, or both —
|
|
127
134
|
* flat or categorized.
|
|
128
135
|
*
|
|
136
|
+
* @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release.
|
|
137
|
+
*
|
|
129
138
|
* @example
|
|
130
139
|
* ```tsx
|
|
131
140
|
* const mention = unstable_useMentionAdapter();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { renderToString } from "react-dom/server";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
|
|
6
|
+
const { message } = vi.hoisted(() => ({
|
|
7
|
+
message: {
|
|
8
|
+
status: { type: "running" as const },
|
|
9
|
+
content: [{ type: "text" as const, text: "streaming" }],
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => ({
|
|
14
|
+
...(await importOriginal<typeof import("@assistant-ui/store")>()),
|
|
15
|
+
useAuiState: (selector: (state: { message: typeof message }) => unknown) =>
|
|
16
|
+
selector({ message }),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
vi.mock("@assistant-ui/store/internal", async (importOriginal) => ({
|
|
20
|
+
...(await importOriginal<typeof import("@assistant-ui/store/internal")>()),
|
|
21
|
+
useShallowSelector: <T,>(selector: T) => selector,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
import { unstable_useMessageStallDetection } from "./useMessageStallDetection";
|
|
25
|
+
|
|
26
|
+
describe("unstable_useMessageStallDetection", () => {
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
vi.restoreAllMocks();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("does not read the wall clock during server render", () => {
|
|
32
|
+
const nowSpy = vi.spyOn(Date, "now");
|
|
33
|
+
const Probe = () => (
|
|
34
|
+
<span>{String(unstable_useMessageStallDetection().stalled)}</span>
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
expect(renderToString(<Probe />)).toBe("<span>false</span>");
|
|
38
|
+
expect(nowSpy).not.toHaveBeenCalled();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from "react";
|
|
|
4
4
|
import { useAuiState } from "@assistant-ui/store";
|
|
5
5
|
import { useShallowSelector } from "@assistant-ui/store/internal";
|
|
6
6
|
|
|
7
|
+
/** @deprecated Experimental since 2026-06-13. Not scheduled for removal; the API may change in any release. */
|
|
7
8
|
export type Unstable_MessageStallDetectionOptions = {
|
|
8
9
|
/**
|
|
9
10
|
* Milliseconds of unchanged message content before the message counts as
|
|
@@ -13,6 +14,7 @@ export type Unstable_MessageStallDetectionOptions = {
|
|
|
13
14
|
thresholdMs?: number | undefined;
|
|
14
15
|
};
|
|
15
16
|
|
|
17
|
+
/** @deprecated Experimental since 2026-06-13. Not scheduled for removal; the API may change in any release. */
|
|
16
18
|
export type Unstable_MessageStallDetection = {
|
|
17
19
|
/** True while the message is running and its content has not changed for at least `thresholdMs`. */
|
|
18
20
|
stalled: boolean;
|
|
@@ -21,8 +23,6 @@ export type Unstable_MessageStallDetection = {
|
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
|
-
* @deprecated Under active development and might change without notice.
|
|
25
|
-
*
|
|
26
26
|
* Detects mid-run output stalls on the current message: while the message is
|
|
27
27
|
* running, watches its text, reasoning, and tool-argument values plus tool-result
|
|
28
28
|
* availability and reports a stall once they stop changing for `thresholdMs`.
|
|
@@ -30,6 +30,8 @@ export type Unstable_MessageStallDetection = {
|
|
|
30
30
|
* provider stalls, after the first tokens have already streamed.
|
|
31
31
|
*
|
|
32
32
|
* Must be used inside a message scope.
|
|
33
|
+
*
|
|
34
|
+
* @deprecated Experimental since 2026-06-13. Not scheduled for removal; the API may change in any release.
|
|
33
35
|
*/
|
|
34
36
|
export function unstable_useMessageStallDetection(
|
|
35
37
|
options?: Unstable_MessageStallDetectionOptions,
|
|
@@ -57,9 +59,9 @@ export function unstable_useMessageStallDetection(
|
|
|
57
59
|
);
|
|
58
60
|
|
|
59
61
|
const running = activity[0] === true;
|
|
60
|
-
const lastActivityRef = useRef(
|
|
62
|
+
const lastActivityRef = useRef(0);
|
|
61
63
|
const [stalled, setStalled] = useState(false);
|
|
62
|
-
const [,
|
|
64
|
+
const [now, setNow] = useState(0);
|
|
63
65
|
|
|
64
66
|
useEffect(() => {
|
|
65
67
|
if (!running) return undefined;
|
|
@@ -73,26 +75,30 @@ export function unstable_useMessageStallDetection(
|
|
|
73
75
|
return undefined;
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
const stall = () => {
|
|
79
|
+
setNow(Date.now());
|
|
80
|
+
setStalled(true);
|
|
81
|
+
};
|
|
76
82
|
const sinceActivity = Date.now() - lastActivityRef.current;
|
|
77
83
|
if (sinceActivity >= thresholdMs) {
|
|
78
|
-
|
|
84
|
+
stall();
|
|
79
85
|
return undefined;
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
setStalled(false);
|
|
83
|
-
const id = setTimeout(
|
|
89
|
+
const id = setTimeout(stall, thresholdMs - sinceActivity);
|
|
84
90
|
return () => clearTimeout(id);
|
|
85
91
|
}, [running, activity, thresholdMs]);
|
|
86
92
|
|
|
87
93
|
useEffect(() => {
|
|
88
94
|
if (!stalled) return undefined;
|
|
89
|
-
const id = setInterval(() =>
|
|
95
|
+
const id = setInterval(() => setNow(Date.now()), 1000);
|
|
90
96
|
return () => clearInterval(id);
|
|
91
97
|
}, [stalled]);
|
|
92
98
|
|
|
93
99
|
if (!stalled) return { stalled: false, stalledForMs: 0 };
|
|
94
100
|
return {
|
|
95
101
|
stalled: true,
|
|
96
|
-
stalledForMs: Math.max(0,
|
|
102
|
+
stalledForMs: Math.max(0, now - lastActivityRef.current),
|
|
97
103
|
};
|
|
98
104
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useMemo, useRef } from "react";
|
|
4
|
+
import { useIsomorphicLayoutEffect } from "../utils/useIsomorphicLayoutEffect";
|
|
4
5
|
import type {
|
|
5
6
|
Unstable_TriggerAdapter,
|
|
6
7
|
Unstable_TriggerItem,
|
|
@@ -8,6 +9,7 @@ import type {
|
|
|
8
9
|
import type { Unstable_IconComponent } from "./useMentionAdapter";
|
|
9
10
|
import { matchesTriggerItemQuery } from "../primitives/composer/trigger/matchesTriggerItemQuery";
|
|
10
11
|
|
|
12
|
+
/** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
|
|
11
13
|
export type Unstable_SlashCommand = {
|
|
12
14
|
readonly id: string;
|
|
13
15
|
readonly label?: string | undefined;
|
|
@@ -16,6 +18,7 @@ export type Unstable_SlashCommand = {
|
|
|
16
18
|
readonly execute: () => void;
|
|
17
19
|
};
|
|
18
20
|
|
|
21
|
+
/** @deprecated Experimental since 2026-04-06. Not scheduled for removal; the API may change in any release. */
|
|
19
22
|
export type Unstable_UseSlashCommandAdapterOptions = {
|
|
20
23
|
readonly commands: readonly Unstable_SlashCommand[];
|
|
21
24
|
/** Strip the trigger text from the composer after executing. @default false */
|
|
@@ -26,19 +29,20 @@ export type Unstable_UseSlashCommandAdapterOptions = {
|
|
|
26
29
|
readonly fallbackIcon?: Unstable_IconComponent;
|
|
27
30
|
};
|
|
28
31
|
|
|
32
|
+
/** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
|
|
29
33
|
export type Unstable_SlashCommandAction = {
|
|
30
34
|
readonly onExecute: (item: Unstable_TriggerItem) => void;
|
|
31
35
|
readonly removeOnExecute?: boolean | undefined;
|
|
32
36
|
};
|
|
33
37
|
|
|
34
38
|
/**
|
|
35
|
-
* @deprecated Under active development and may change without notice.
|
|
36
|
-
*
|
|
37
39
|
* Bundles slash command definitions (with inline `execute` callbacks) into
|
|
38
40
|
* `{adapter, action}` that plug directly into `ComposerTriggerPopover`.
|
|
39
41
|
* `execute` stays in the hook closure and is never attached to the returned
|
|
40
42
|
* `TriggerItem`, keeping items serializable.
|
|
41
43
|
*
|
|
44
|
+
* @deprecated Experimental since 2026-04-06. Not scheduled for removal; the API may change in any release.
|
|
45
|
+
*
|
|
42
46
|
* @example
|
|
43
47
|
* ```tsx
|
|
44
48
|
* const slash = unstable_useSlashCommandAdapter({
|
|
@@ -71,7 +75,7 @@ export function unstable_useSlashCommandAdapter(
|
|
|
71
75
|
: nextItems;
|
|
72
76
|
|
|
73
77
|
// The adapter's callbacks must only observe commands from committed renders.
|
|
74
|
-
|
|
78
|
+
useIsomorphicLayoutEffect(() => {
|
|
75
79
|
commandsRef.current = commands;
|
|
76
80
|
committedItemsRef.current = items;
|
|
77
81
|
}, [commands, items]);
|
|
@@ -20,10 +20,12 @@ import {
|
|
|
20
20
|
type ModelContextSnapshotSource,
|
|
21
21
|
} from "../useModelContextSnapshot";
|
|
22
22
|
|
|
23
|
+
/** @deprecated Experimental since 2026-08-29. Not scheduled for removal; the API may change in any release. */
|
|
23
24
|
export type Unstable_WebMcpProviderOptions = {
|
|
24
25
|
filter?: (name: string, tool: Tool<any, any>) => boolean;
|
|
25
26
|
};
|
|
26
27
|
|
|
28
|
+
/** @deprecated Experimental since 2026-08-29. Not scheduled for removal; the API may change in any release. */
|
|
27
29
|
export type Unstable_WebMcpProviderResult = {
|
|
28
30
|
status: "unsupported" | "active";
|
|
29
31
|
registeredToolNames: readonly string[];
|
|
@@ -128,6 +130,8 @@ const useWebMcpRegistry = ({
|
|
|
128
130
|
* Returns `status: "unsupported"` when the page exposes no
|
|
129
131
|
* `document.modelContext` (or `navigator.modelContext`), and the sorted names
|
|
130
132
|
* of the tools currently registered with the host.
|
|
133
|
+
*
|
|
134
|
+
* @deprecated Experimental since 2026-08-29. Not scheduled for removal; the API may change in any release.
|
|
131
135
|
*/
|
|
132
136
|
export const unstable_useWebMcpProvider = (
|
|
133
137
|
options: Unstable_WebMcpProviderOptions = {},
|
|
@@ -30,6 +30,237 @@ afterEach(() => {
|
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
describe("getSelectionMessageId", () => {
|
|
33
|
+
it.each([false, true])(
|
|
34
|
+
"accepts an end at the next message's first text offset zero (backward: %s)",
|
|
35
|
+
(backward) => {
|
|
36
|
+
document.body.innerHTML =
|
|
37
|
+
'<div data-message-id="message-1"><p id="first" data-aui-quote-selectable>first text</p></div><div data-message-id="message-2"><p data-aui-quote-selectable><span id="second">second text</span></p></div>';
|
|
38
|
+
const first = textNode("#first");
|
|
39
|
+
const second = textNode("#second");
|
|
40
|
+
const selection = selectText(first);
|
|
41
|
+
if (backward) {
|
|
42
|
+
selection.setBaseAndExtent(second, 0, first, 1);
|
|
43
|
+
} else {
|
|
44
|
+
selection.setBaseAndExtent(first, 1, second, 0);
|
|
45
|
+
}
|
|
46
|
+
const endpoints = [
|
|
47
|
+
selection.anchorNode,
|
|
48
|
+
selection.anchorOffset,
|
|
49
|
+
selection.focusNode,
|
|
50
|
+
selection.focusOffset,
|
|
51
|
+
];
|
|
52
|
+
const text = selection.toString();
|
|
53
|
+
|
|
54
|
+
expect(getSelectionMessageId(selection)).toBe("message-1");
|
|
55
|
+
expect(selection.toString()).toBe(text);
|
|
56
|
+
expect([
|
|
57
|
+
selection.anchorNode,
|
|
58
|
+
selection.anchorOffset,
|
|
59
|
+
selection.focusNode,
|
|
60
|
+
selection.focusOffset,
|
|
61
|
+
]).toEqual(endpoints);
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
it("accepts an end at the next message element's offset zero", () => {
|
|
66
|
+
document.body.innerHTML =
|
|
67
|
+
'<div data-message-id="message-1"><p id="first" data-aui-quote-selectable>first text</p></div><div id="second" data-message-id="message-2"><p>second text</p></div>';
|
|
68
|
+
const selection = selectText(textNode("#first"));
|
|
69
|
+
const second = document.querySelector("#second");
|
|
70
|
+
assert(second);
|
|
71
|
+
selection.getRangeAt(0).setEnd(second, 0);
|
|
72
|
+
|
|
73
|
+
expect(getSelectionMessageId(selection)).toBe("message-1");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it.each([false, true])(
|
|
77
|
+
"accepts a zero-offset end inside an excluded next message (backward: %s)",
|
|
78
|
+
(backward) => {
|
|
79
|
+
document.body.innerHTML =
|
|
80
|
+
'<div data-message-id="message-1"><p id="first" data-aui-quote-selectable>first text</p></div><div data-message-id="message-2" data-aui-quote-selectable="false"><p><span id="second">second text</span></p></div>';
|
|
81
|
+
const first = textNode("#first");
|
|
82
|
+
const second = textNode("#second");
|
|
83
|
+
const selection = selectText(first);
|
|
84
|
+
selection.setBaseAndExtent(
|
|
85
|
+
backward ? second : first,
|
|
86
|
+
0,
|
|
87
|
+
backward ? first : second,
|
|
88
|
+
0,
|
|
89
|
+
);
|
|
90
|
+
const endpoints = [
|
|
91
|
+
selection.anchorNode,
|
|
92
|
+
selection.anchorOffset,
|
|
93
|
+
selection.focusNode,
|
|
94
|
+
selection.focusOffset,
|
|
95
|
+
];
|
|
96
|
+
const text = selection.toString();
|
|
97
|
+
|
|
98
|
+
expect(getSelectionMessageId(selection)).toBe("message-1");
|
|
99
|
+
expect(selection.toString()).toBe(text);
|
|
100
|
+
expect([
|
|
101
|
+
selection.anchorNode,
|
|
102
|
+
selection.anchorOffset,
|
|
103
|
+
selection.focusNode,
|
|
104
|
+
selection.focusOffset,
|
|
105
|
+
]).toEqual(endpoints);
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
it.each(["<span></span>", "<span> </span>", "<span>excluded text</span>"])(
|
|
110
|
+
"rejects selected content inheriting the next message's exclusion: %s",
|
|
111
|
+
(prefix) => {
|
|
112
|
+
document.body.innerHTML = `<div data-message-id="message-1"><p id="first" data-aui-quote-selectable>first text</p></div><div data-message-id="message-2" data-aui-quote-selectable="false">${prefix}<p id="second">second text</p></div>`;
|
|
113
|
+
const first = textNode("#first");
|
|
114
|
+
const second = textNode("#second");
|
|
115
|
+
const selection = selectText(first);
|
|
116
|
+
for (const backward of [false, true]) {
|
|
117
|
+
selection.setBaseAndExtent(
|
|
118
|
+
backward ? second : first,
|
|
119
|
+
0,
|
|
120
|
+
backward ? first : second,
|
|
121
|
+
0,
|
|
122
|
+
);
|
|
123
|
+
expect(getSelectionMessageId(selection)).toBeNull();
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
it.each([
|
|
129
|
+
{ footer: "", separator: "", prefix: "<span></span>" },
|
|
130
|
+
{ footer: "", separator: "\n ", prefix: "" },
|
|
131
|
+
{
|
|
132
|
+
footer: '<div class="footer"><button><svg></svg></button></div>',
|
|
133
|
+
separator: "",
|
|
134
|
+
prefix: "",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
footer: "<div><span></span></div>",
|
|
138
|
+
separator: "\n",
|
|
139
|
+
prefix: "<span></span>\n",
|
|
140
|
+
},
|
|
141
|
+
])(
|
|
142
|
+
"skips empty chrome and whitespace at the end: %j",
|
|
143
|
+
({ footer, separator, prefix }) => {
|
|
144
|
+
document.body.innerHTML = `<div data-message-id="message-1"><p id="first" data-aui-quote-selectable>first text</p>${footer}</div>${separator}<div data-message-id="message-2">${prefix}<p id="second">second text</p></div>`;
|
|
145
|
+
const first = textNode("#first");
|
|
146
|
+
const second = textNode("#second");
|
|
147
|
+
const selection = selectText(first);
|
|
148
|
+
for (const backward of [false, true]) {
|
|
149
|
+
selection.setBaseAndExtent(
|
|
150
|
+
backward ? second : first,
|
|
151
|
+
backward ? 0 : 1,
|
|
152
|
+
backward ? first : second,
|
|
153
|
+
backward ? 1 : 0,
|
|
154
|
+
);
|
|
155
|
+
const endpoints = [
|
|
156
|
+
selection.anchorNode,
|
|
157
|
+
selection.anchorOffset,
|
|
158
|
+
selection.focusNode,
|
|
159
|
+
selection.focusOffset,
|
|
160
|
+
];
|
|
161
|
+
const text = selection.toString();
|
|
162
|
+
expect(getSelectionMessageId(selection)).toBe("message-1");
|
|
163
|
+
expect(selection.toString()).toBe(text);
|
|
164
|
+
expect([
|
|
165
|
+
selection.anchorNode,
|
|
166
|
+
selection.anchorOffset,
|
|
167
|
+
selection.focusNode,
|
|
168
|
+
selection.focusOffset,
|
|
169
|
+
]).toEqual(endpoints);
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
it.each([
|
|
175
|
+
{ footer: "<footer>footer text</footer>", prefix: "" },
|
|
176
|
+
{ footer: "", prefix: "<header>next message header</header>" },
|
|
177
|
+
{
|
|
178
|
+
footer: '<div data-aui-quote-selectable="false"><span></span>\n</div>',
|
|
179
|
+
prefix: "",
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
footer: "",
|
|
183
|
+
prefix: '<div data-aui-quote-selectable="false"><span></span>\n</div>',
|
|
184
|
+
},
|
|
185
|
+
])(
|
|
186
|
+
"does not trim selected content or exclusions: %j",
|
|
187
|
+
({ footer, prefix }) => {
|
|
188
|
+
document.body.innerHTML = `<div data-message-id="message-1"><p id="first" data-aui-quote-selectable>first text</p>${footer}</div>\n<div data-message-id="message-2">${prefix}<p id="second">second text</p></div>`;
|
|
189
|
+
const selection = selectText(textNode("#first"));
|
|
190
|
+
selection.getRangeAt(0).setEnd(textNode("#second"), 0);
|
|
191
|
+
expect(getSelectionMessageId(selection)).toBeNull();
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
it("does not normalize past the selection start", () => {
|
|
196
|
+
document.body.innerHTML =
|
|
197
|
+
'<div data-message-id="message-1"><p id="first">first text</p><span></span></div><div data-message-id="message-2"><p id="second">second text</p></div>';
|
|
198
|
+
const first = textNode("#first");
|
|
199
|
+
const selection = selectText(first);
|
|
200
|
+
selection.setBaseAndExtent(first, first.length, textNode("#second"), 0);
|
|
201
|
+
expect(getSelectionMessageId(selection)).toBeNull();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it.each(["<span>\n </span>", "<span></span>"])(
|
|
205
|
+
"preserves inherited exclusions after a nested opt-in: %s",
|
|
206
|
+
(suffix) => {
|
|
207
|
+
document.body.innerHTML = `<div data-message-id="message-1"><div data-aui-quote-selectable="false"><p id="first" data-aui-quote-selectable>first text</p>${suffix}</div></div><div data-message-id="message-2"><p id="second">second text</p></div>`;
|
|
208
|
+
const first = textNode("#first");
|
|
209
|
+
const second = textNode("#second");
|
|
210
|
+
const selection = selectText(first);
|
|
211
|
+
expect(getSelectionMessageId(selection)).toBe("message-1");
|
|
212
|
+
for (const backward of [false, true]) {
|
|
213
|
+
selection.setBaseAndExtent(
|
|
214
|
+
backward ? second : first,
|
|
215
|
+
0,
|
|
216
|
+
backward ? first : second,
|
|
217
|
+
0,
|
|
218
|
+
);
|
|
219
|
+
expect(getSelectionMessageId(selection)).toBeNull();
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
it("rejects even one selected character in the next message", () => {
|
|
225
|
+
document.body.innerHTML =
|
|
226
|
+
'<div data-message-id="message-1"><p id="first">first text</p></div><div data-message-id="message-2"><p id="second">second text</p></div>';
|
|
227
|
+
const selection = selectText(textNode("#first"));
|
|
228
|
+
selection.getRangeAt(0).setEnd(textNode("#second"), 1);
|
|
229
|
+
|
|
230
|
+
expect(getSelectionMessageId(selection)).toBeNull();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it.each(["", '<span aria-hidden="true"></span>'])(
|
|
234
|
+
"rejects an empty excluded subtree before a zero-offset end with suffix %s",
|
|
235
|
+
(suffix) => {
|
|
236
|
+
document.body.innerHTML = `<div data-message-id="message-1" data-aui-quote-selectable><p id="first">first text</p><span data-aui-quote-selectable="false"></span>${suffix}</div><div data-message-id="message-2"><p id="second">second text</p></div>`;
|
|
237
|
+
const selection = selectText(textNode("#first"));
|
|
238
|
+
selection.getRangeAt(0).setEnd(textNode("#second"), 0);
|
|
239
|
+
|
|
240
|
+
expect(getSelectionMessageId(selection)).toBeNull();
|
|
241
|
+
},
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
it("rejects an excluded subtree preceding the next message's text", () => {
|
|
245
|
+
document.body.innerHTML =
|
|
246
|
+
'<div data-message-id="message-1"><p id="first">first text</p></div><div data-message-id="message-2"><span data-aui-quote-selectable="false"></span><p id="second">second text</p></div>';
|
|
247
|
+
const selection = selectText(textNode("#first"));
|
|
248
|
+
selection.getRangeAt(0).setEnd(textNode("#second"), 0);
|
|
249
|
+
|
|
250
|
+
expect(getSelectionMessageId(selection)).toBeNull();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("still checks thread ownership after adjusting a zero-offset end", () => {
|
|
254
|
+
document.body.innerHTML =
|
|
255
|
+
'<div id="first-thread"><div data-message-id="message-1"><p id="first">first text</p></div><div data-message-id="message-2"><p id="second">second text</p></div></div><div id="second-thread"></div>';
|
|
256
|
+
const selection = selectText(textNode("#first"));
|
|
257
|
+
selection.getRangeAt(0).setEnd(textNode("#second"), 0);
|
|
258
|
+
const root = document.querySelector("#second-thread");
|
|
259
|
+
assert(root);
|
|
260
|
+
|
|
261
|
+
expect(getSelectionMessageId(selection, root)).toBeNull();
|
|
262
|
+
});
|
|
263
|
+
|
|
33
264
|
it("rejects a message outside the supplied thread root", () => {
|
|
34
265
|
document.body.innerHTML = `
|
|
35
266
|
<div id="first-thread">
|