@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
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/** @vitest-environment jsdom */
|
|
2
|
+
import { act, fireEvent, render, screen } from "@testing-library/react";
|
|
3
|
+
import type { ComponentProps, PropsWithChildren, ReactNode } from "react";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import {
|
|
6
|
+
AssistantRuntimeProvider,
|
|
7
|
+
useExternalStoreRuntime,
|
|
8
|
+
} from "@assistant-ui/core/react";
|
|
9
|
+
import type { ThreadMessageLike } from "@assistant-ui/core";
|
|
10
|
+
import { ComposerPrimitiveRoot } from "./ComposerRoot";
|
|
11
|
+
import { ComposerPrimitiveInput } from "./ComposerInput";
|
|
12
|
+
import { ComposerPrimitiveCancel } from "./ComposerCancel";
|
|
13
|
+
import { ThreadPrimitiveRoot } from "../thread/ThreadRoot";
|
|
14
|
+
import { ThreadPrimitiveViewportProvider } from "../../context/providers/ThreadViewportProvider";
|
|
15
|
+
import { ThreadPrimitiveMessages } from "../thread/ThreadMessages";
|
|
16
|
+
import { ActionBarPrimitiveEdit } from "../actionBar/ActionBarEdit";
|
|
17
|
+
|
|
18
|
+
const messages = [{ id: "user-message", text: "Original message" }];
|
|
19
|
+
const { cancelOverride } = vi.hoisted(() => ({ cancelOverride: vi.fn() }));
|
|
20
|
+
vi.mock("@assistant-ui/core/react", async (importOriginal) => {
|
|
21
|
+
const original =
|
|
22
|
+
await importOriginal<typeof import("@assistant-ui/core/react")>();
|
|
23
|
+
return {
|
|
24
|
+
...original,
|
|
25
|
+
useComposerCancel: () => {
|
|
26
|
+
const behavior = original.useComposerCancel();
|
|
27
|
+
return cancelOverride() ?? behavior;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
const convertMessage = (
|
|
32
|
+
message: (typeof messages)[number],
|
|
33
|
+
): ThreadMessageLike => ({
|
|
34
|
+
id: message.id,
|
|
35
|
+
role: "user",
|
|
36
|
+
content: [{ type: "text", text: message.text }],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const Provider = ({
|
|
40
|
+
children,
|
|
41
|
+
isRunning = false,
|
|
42
|
+
onCancel,
|
|
43
|
+
}: PropsWithChildren<{
|
|
44
|
+
isRunning?: boolean;
|
|
45
|
+
onCancel?: () => Promise<void>;
|
|
46
|
+
}>) => {
|
|
47
|
+
const runtime = useExternalStoreRuntime({
|
|
48
|
+
messages,
|
|
49
|
+
convertMessage,
|
|
50
|
+
onNew: async () => {},
|
|
51
|
+
onEdit: async () => {},
|
|
52
|
+
isRunning,
|
|
53
|
+
onCancel,
|
|
54
|
+
});
|
|
55
|
+
return (
|
|
56
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
57
|
+
{children}
|
|
58
|
+
</AssistantRuntimeProvider>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const Message = () => (
|
|
63
|
+
<ActionBarPrimitiveEdit>Edit message</ActionBarPrimitiveEdit>
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
type Options = {
|
|
67
|
+
nonForm?: boolean;
|
|
68
|
+
disabledMain?: boolean;
|
|
69
|
+
showMain?: boolean;
|
|
70
|
+
contentEditable?: boolean;
|
|
71
|
+
beforeMain?: ReactNode;
|
|
72
|
+
onCancelClick?: ComponentProps<typeof ComposerPrimitiveCancel>["onClick"];
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const setup = ({
|
|
76
|
+
nonForm,
|
|
77
|
+
disabledMain,
|
|
78
|
+
showMain = true,
|
|
79
|
+
contentEditable,
|
|
80
|
+
beforeMain,
|
|
81
|
+
onCancelClick,
|
|
82
|
+
}: Options = {}) => {
|
|
83
|
+
const EditComposer = () => (
|
|
84
|
+
<ComposerPrimitiveRoot render={nonForm ? <div /> : undefined}>
|
|
85
|
+
<ComposerPrimitiveInput aria-label="Edit composer" autoFocus />
|
|
86
|
+
<ComposerPrimitiveCancel onClick={onCancelClick}>
|
|
87
|
+
Cancel edit
|
|
88
|
+
</ComposerPrimitiveCancel>
|
|
89
|
+
</ComposerPrimitiveRoot>
|
|
90
|
+
);
|
|
91
|
+
render(
|
|
92
|
+
<Provider>
|
|
93
|
+
<input aria-label="Outside thread" />
|
|
94
|
+
<ThreadPrimitiveRoot>
|
|
95
|
+
<ThreadPrimitiveViewportProvider>
|
|
96
|
+
<ThreadPrimitiveMessages components={{ Message, EditComposer }} />
|
|
97
|
+
{beforeMain}
|
|
98
|
+
{showMain && (
|
|
99
|
+
<ComposerPrimitiveRoot render={nonForm ? <section /> : undefined}>
|
|
100
|
+
{contentEditable ? (
|
|
101
|
+
<div
|
|
102
|
+
role="textbox"
|
|
103
|
+
aria-label="Main composer"
|
|
104
|
+
contentEditable
|
|
105
|
+
/>
|
|
106
|
+
) : (
|
|
107
|
+
<ComposerPrimitiveInput
|
|
108
|
+
aria-label="Main composer"
|
|
109
|
+
disabled={disabledMain}
|
|
110
|
+
/>
|
|
111
|
+
)}
|
|
112
|
+
</ComposerPrimitiveRoot>
|
|
113
|
+
)}
|
|
114
|
+
</ThreadPrimitiveViewportProvider>
|
|
115
|
+
</ThreadPrimitiveRoot>
|
|
116
|
+
</Provider>,
|
|
117
|
+
);
|
|
118
|
+
fireEvent.click(screen.getByRole("button", { name: "Edit message" }));
|
|
119
|
+
const input = screen.getByRole("textbox", { name: "Edit composer" });
|
|
120
|
+
act(() => input.focus());
|
|
121
|
+
return { input, cancel: screen.getByRole("button", { name: "Cancel edit" }) };
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
afterEach(() => {
|
|
125
|
+
cancelOverride.mockReset();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("edit composer cancellation focus", () => {
|
|
129
|
+
it.each(["Escape", "Cancel"])(
|
|
130
|
+
"returns focus to the main composer after %s",
|
|
131
|
+
(action) => {
|
|
132
|
+
const { input, cancel } = setup();
|
|
133
|
+
if (action === "Escape") {
|
|
134
|
+
fireEvent.keyDown(input, { key: "Escape" });
|
|
135
|
+
} else {
|
|
136
|
+
act(() => cancel.focus());
|
|
137
|
+
fireEvent.click(cancel);
|
|
138
|
+
}
|
|
139
|
+
expect(
|
|
140
|
+
screen.queryByRole("textbox", { name: "Edit composer" }),
|
|
141
|
+
).toBeNull();
|
|
142
|
+
expect(document.activeElement).toBe(
|
|
143
|
+
screen.getByRole("textbox", { name: "Main composer" }),
|
|
144
|
+
);
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
it("supports non-form composer roots and a contenteditable destination", () => {
|
|
149
|
+
const { input } = setup({ nonForm: true, contentEditable: true });
|
|
150
|
+
fireEvent.keyDown(input, { key: "Escape" });
|
|
151
|
+
expect(document.activeElement).toBe(
|
|
152
|
+
screen.getByRole("textbox", { name: "Main composer" }),
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("does not focus a nested thread's composer", () => {
|
|
157
|
+
const { input } = setup({
|
|
158
|
+
beforeMain: (
|
|
159
|
+
<Provider>
|
|
160
|
+
<ThreadPrimitiveRoot>
|
|
161
|
+
<ComposerPrimitiveRoot>
|
|
162
|
+
<textarea aria-label="Nested composer" />
|
|
163
|
+
</ComposerPrimitiveRoot>
|
|
164
|
+
</ThreadPrimitiveRoot>
|
|
165
|
+
</Provider>
|
|
166
|
+
),
|
|
167
|
+
});
|
|
168
|
+
fireEvent.keyDown(input, { key: "Escape" });
|
|
169
|
+
expect(document.activeElement).toBe(
|
|
170
|
+
screen.getByRole("textbox", { name: "Main composer" }),
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it.each(["hidden", "inert"] as const)(
|
|
175
|
+
"skips a %s main composer before the usable destination",
|
|
176
|
+
(attribute) => {
|
|
177
|
+
const { input } = setup({
|
|
178
|
+
beforeMain: (
|
|
179
|
+
<ComposerPrimitiveRoot {...{ [attribute]: true }}>
|
|
180
|
+
<textarea aria-label="Unavailable composer" />
|
|
181
|
+
</ComposerPrimitiveRoot>
|
|
182
|
+
),
|
|
183
|
+
});
|
|
184
|
+
fireEvent.keyDown(input, { key: "Escape" });
|
|
185
|
+
expect(document.activeElement).toBe(
|
|
186
|
+
screen.getByRole("textbox", { name: "Main composer" }),
|
|
187
|
+
);
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
it.each([{ showMain: false }, { disabledMain: true }])(
|
|
192
|
+
"cancels safely without a usable destination: %j",
|
|
193
|
+
(options) => {
|
|
194
|
+
const { input } = setup(options);
|
|
195
|
+
fireEvent.keyDown(input, { key: "Escape" });
|
|
196
|
+
expect(
|
|
197
|
+
screen.queryByRole("textbox", { name: "Edit composer" }),
|
|
198
|
+
).toBeNull();
|
|
199
|
+
expect(document.activeElement).toBe(document.body);
|
|
200
|
+
},
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
it("preserves focus deliberately moved by the consumer", () => {
|
|
204
|
+
const { cancel } = setup({
|
|
205
|
+
onCancelClick: () =>
|
|
206
|
+
screen.getByRole("textbox", { name: "Outside thread" }).focus(),
|
|
207
|
+
});
|
|
208
|
+
act(() => cancel.focus());
|
|
209
|
+
fireEvent.click(cancel);
|
|
210
|
+
expect(screen.queryByRole("textbox", { name: "Edit composer" })).toBeNull();
|
|
211
|
+
expect(document.activeElement).toBe(
|
|
212
|
+
screen.getByRole("textbox", { name: "Outside thread" }),
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("restores focus when cancellation hides the connected edit composer", () => {
|
|
217
|
+
cancelOverride.mockReturnValue({
|
|
218
|
+
disabled: false,
|
|
219
|
+
cancel: () => {
|
|
220
|
+
const active = document.activeElement as HTMLElement;
|
|
221
|
+
active.closest<HTMLElement>('[data-aui-composer-type="edit"]')!.hidden =
|
|
222
|
+
true;
|
|
223
|
+
active.blur();
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
const { cancel } = setup();
|
|
227
|
+
act(() => cancel.focus());
|
|
228
|
+
fireEvent.click(cancel);
|
|
229
|
+
|
|
230
|
+
expect(cancel.isConnected).toBe(true);
|
|
231
|
+
expect(cancel.closest("[hidden]")).not.toBeNull();
|
|
232
|
+
expect(document.activeElement).toBe(
|
|
233
|
+
screen.getByRole("textbox", { name: "Main composer" }),
|
|
234
|
+
);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("does not cancel or restore focus when the consumer prevents the action", () => {
|
|
238
|
+
const { cancel } = setup({
|
|
239
|
+
onCancelClick: (event) => event.preventDefault(),
|
|
240
|
+
});
|
|
241
|
+
act(() => cancel.focus());
|
|
242
|
+
fireEvent.click(cancel);
|
|
243
|
+
expect(
|
|
244
|
+
screen.queryByRole("textbox", { name: "Edit composer" }),
|
|
245
|
+
).not.toBeNull();
|
|
246
|
+
expect(document.activeElement).toBe(cancel);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("leaves focus and editing unchanged when cancellation throws", () => {
|
|
250
|
+
const error = new Error("Cancellation failed");
|
|
251
|
+
const cancel = vi.fn(() => {
|
|
252
|
+
throw error;
|
|
253
|
+
});
|
|
254
|
+
cancelOverride.mockReturnValue({
|
|
255
|
+
disabled: false,
|
|
256
|
+
cancel,
|
|
257
|
+
});
|
|
258
|
+
const { cancel: button } = setup();
|
|
259
|
+
act(() => button.focus());
|
|
260
|
+
const onError = (event: ErrorEvent) => {
|
|
261
|
+
if (event.error === error) event.preventDefault();
|
|
262
|
+
};
|
|
263
|
+
window.addEventListener("error", onError);
|
|
264
|
+
try {
|
|
265
|
+
fireEvent.click(button);
|
|
266
|
+
expect(cancel).toHaveBeenCalledOnce();
|
|
267
|
+
expect(
|
|
268
|
+
screen.queryByRole("textbox", { name: "Edit composer" }),
|
|
269
|
+
).not.toBeNull();
|
|
270
|
+
expect(document.activeElement).toBe(button);
|
|
271
|
+
} finally {
|
|
272
|
+
window.removeEventListener("error", onError);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it.each(["input", "button"])(
|
|
277
|
+
"does not restore focus after pointer cancellation with focus on the %s",
|
|
278
|
+
(source) => {
|
|
279
|
+
const { input, cancel } = setup();
|
|
280
|
+
act(() => (source === "input" ? input : cancel).focus());
|
|
281
|
+
fireEvent.click(cancel, { detail: 1 });
|
|
282
|
+
expect(
|
|
283
|
+
screen.queryByRole("textbox", { name: "Edit composer" }),
|
|
284
|
+
).toBeNull();
|
|
285
|
+
expect(document.activeElement).toBe(document.body);
|
|
286
|
+
},
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
it("does not move focus when cancelling a running thread", () => {
|
|
290
|
+
const onCancel = vi.fn(async () => {});
|
|
291
|
+
render(
|
|
292
|
+
<Provider isRunning onCancel={onCancel}>
|
|
293
|
+
<ThreadPrimitiveRoot>
|
|
294
|
+
<ComposerPrimitiveRoot>
|
|
295
|
+
<textarea aria-label="Main composer" />
|
|
296
|
+
<ComposerPrimitiveCancel>Stop run</ComposerPrimitiveCancel>
|
|
297
|
+
</ComposerPrimitiveRoot>
|
|
298
|
+
</ThreadPrimitiveRoot>
|
|
299
|
+
</Provider>,
|
|
300
|
+
);
|
|
301
|
+
const cancel = screen.getByRole("button", { name: "Stop run" });
|
|
302
|
+
act(() => cancel.focus());
|
|
303
|
+
fireEvent.click(cancel);
|
|
304
|
+
expect(onCancel).toHaveBeenCalledOnce();
|
|
305
|
+
expect(document.activeElement).toBe(cancel);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
@@ -5,12 +5,16 @@ import {
|
|
|
5
5
|
type ActionButtonProps,
|
|
6
6
|
createActionButton,
|
|
7
7
|
} from "../../utils/createActionButton";
|
|
8
|
+
import type { MouseEvent } from "react";
|
|
8
9
|
import { useComposerCancel as useComposerCancelBehavior } from "@assistant-ui/core/react";
|
|
10
|
+
import { useComposerCancelWithFocus } from "./useComposerCancelWithFocus";
|
|
9
11
|
|
|
10
12
|
const useComposerCancel = () => {
|
|
11
13
|
const { disabled, cancel } = useComposerCancelBehavior();
|
|
14
|
+
const cancelWithFocus = useComposerCancelWithFocus(cancel);
|
|
12
15
|
if (disabled) return null;
|
|
13
|
-
return
|
|
16
|
+
return (event: MouseEvent<HTMLButtonElement>) =>
|
|
17
|
+
cancelWithFocus(event.detail === 0 ? event.currentTarget : null);
|
|
14
18
|
};
|
|
15
19
|
|
|
16
20
|
export namespace ComposerPrimitiveCancel {
|
|
@@ -27,6 +31,9 @@ export namespace ComposerPrimitiveCancel {
|
|
|
27
31
|
*
|
|
28
32
|
* This component automatically handles the cancel functionality and is disabled
|
|
29
33
|
* when canceling is not available.
|
|
34
|
+
* Keyboard or assistive-technology activation after editing returns focus to the
|
|
35
|
+
* main thread composer when available, unless a handler has moved focus elsewhere.
|
|
36
|
+
* Pointer activation preserves the existing focus behavior.
|
|
30
37
|
*
|
|
31
38
|
* @example
|
|
32
39
|
* ```tsx
|
|
@@ -27,6 +27,7 @@ import { useAui } from "@assistant-ui/store";
|
|
|
27
27
|
import { flushTapSync } from "@assistant-ui/tap";
|
|
28
28
|
import { useComposerInputPluginRegistryOptional } from "./ComposerInputPluginContext";
|
|
29
29
|
import { useComposerCompactContextOptional } from "./ComposerCompactContext";
|
|
30
|
+
import { useComposerCancelWithFocus } from "./useComposerCancelWithFocus";
|
|
30
31
|
import {
|
|
31
32
|
useComposerInputDisabled,
|
|
32
33
|
useComposerInputValue,
|
|
@@ -50,21 +51,32 @@ export namespace ComposerPrimitiveInput {
|
|
|
50
51
|
render?: ReactElement | undefined;
|
|
51
52
|
/**
|
|
52
53
|
* Whether to cancel message composition when Escape is pressed.
|
|
54
|
+
* After cancelling an edit, focus returns to the main thread composer when
|
|
55
|
+
* available, unless a handler has moved focus elsewhere.
|
|
53
56
|
* @default true
|
|
54
57
|
*/
|
|
55
58
|
cancelOnEscape?: boolean | undefined;
|
|
56
59
|
/**
|
|
57
60
|
* Whether to automatically focus the input when a new run starts.
|
|
61
|
+
*
|
|
62
|
+
* @deprecated Experimental since 2024-10-12. Not scheduled for removal; the API may change in any release.
|
|
63
|
+
*
|
|
58
64
|
* @default true
|
|
59
65
|
*/
|
|
60
66
|
unstable_focusOnRunStart?: boolean | undefined;
|
|
61
67
|
/**
|
|
62
68
|
* Whether to automatically focus the input when scrolling to bottom.
|
|
69
|
+
*
|
|
70
|
+
* @deprecated Experimental since 2024-10-12. Not scheduled for removal; the API may change in any release.
|
|
71
|
+
*
|
|
63
72
|
* @default true
|
|
64
73
|
*/
|
|
65
74
|
unstable_focusOnScrollToBottom?: boolean | undefined;
|
|
66
75
|
/**
|
|
67
76
|
* Whether to automatically focus the input when switching threads.
|
|
77
|
+
*
|
|
78
|
+
* @deprecated Experimental since 2024-10-12. Not scheduled for removal; the API may change in any release.
|
|
79
|
+
*
|
|
68
80
|
* @default true
|
|
69
81
|
*/
|
|
70
82
|
unstable_focusOnThreadSwitched?: boolean | undefined;
|
|
@@ -73,6 +85,9 @@ export namespace ComposerPrimitiveInput {
|
|
|
73
85
|
* instead of submitting, detected via
|
|
74
86
|
* `(pointer: coarse) and (not (any-pointer: fine))`. Only takes effect
|
|
75
87
|
* when `submitMode` resolves to `"enter"`.
|
|
88
|
+
*
|
|
89
|
+
* @deprecated Experimental since 2026-05-24. Not scheduled for removal; the API may change in any release.
|
|
90
|
+
*
|
|
76
91
|
* @default false
|
|
77
92
|
*/
|
|
78
93
|
unstable_insertNewlineOnTouchEnter?: boolean | undefined;
|
|
@@ -119,7 +134,7 @@ export namespace ComposerPrimitiveInput {
|
|
|
119
134
|
* keyboard shortcuts, file paste support, and intelligent focus management.
|
|
120
135
|
* It integrates with the composer context to manage message state and submission.
|
|
121
136
|
*
|
|
122
|
-
* When rendered inside `
|
|
137
|
+
* When rendered inside `TriggerPopoverRoot` and a popover is open, the
|
|
123
138
|
* underlying `<textarea>` automatically receives `aria-controls`,
|
|
124
139
|
* `aria-expanded`, `aria-haspopup`, and `aria-activedescendant` for the
|
|
125
140
|
* combobox relationship. These computed attributes override user-provided
|
|
@@ -192,6 +207,9 @@ export const ComposerPrimitiveInput = forwardRef<
|
|
|
192
207
|
const value = useComposerInputValue();
|
|
193
208
|
const isDisabled = useComposerInputDisabled(disabledProp);
|
|
194
209
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
210
|
+
const cancel = useComposerCancelWithFocus(
|
|
211
|
+
useCallback(() => aui.composer.cancel(), [aui]),
|
|
212
|
+
);
|
|
195
213
|
const ref = useComposedRefs(forwardedRef, textareaRef);
|
|
196
214
|
// suppress text/cursor broadcasts during IME composition
|
|
197
215
|
const compositionRef = useRef(false);
|
|
@@ -214,7 +232,7 @@ export const ComposerPrimitiveInput = forwardRef<
|
|
|
214
232
|
|
|
215
233
|
const composer = aui.composer;
|
|
216
234
|
if (composer.getState().canCancel) {
|
|
217
|
-
|
|
235
|
+
cancel(textareaRef.current);
|
|
218
236
|
e.preventDefault();
|
|
219
237
|
}
|
|
220
238
|
});
|
|
@@ -14,11 +14,10 @@ import {
|
|
|
14
14
|
} from "react";
|
|
15
15
|
import { useComposerSend } from "./ComposerSend";
|
|
16
16
|
import { ComposerCompactContext } from "./ComposerCompactContext";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const COMPOSER_INPUT_SELECTOR = `textarea:not(:disabled), ${CONTENT_EDITABLE_SELECTOR}`;
|
|
17
|
+
import {
|
|
18
|
+
COMPOSER_INPUT_SELECTOR,
|
|
19
|
+
CONTENT_EDITABLE_SELECTOR,
|
|
20
|
+
} from "./composerInputSelector";
|
|
22
21
|
|
|
23
22
|
// Keeps tabindex="-1": roving-tabindex widgets click-focus items at -1.
|
|
24
23
|
const INTERACTIVE_ELEMENT_SELECTOR = [
|
|
@@ -82,6 +81,7 @@ export const ComposerPrimitiveRoot = forwardRef<
|
|
|
82
81
|
ComposerPrimitiveRoot.Props
|
|
83
82
|
>(({ onSubmit, onMouseDown, compact, ...rest }, forwardedRef) => {
|
|
84
83
|
const send = useComposerSend();
|
|
84
|
+
const composerType = useAuiState((s) => s.composer.type);
|
|
85
85
|
|
|
86
86
|
const [multiline, setMultiline] = useState(false);
|
|
87
87
|
const compactContext = useMemo(() => ({ setMultiline }), []);
|
|
@@ -126,6 +126,7 @@ export const ComposerPrimitiveRoot = forwardRef<
|
|
|
126
126
|
<ComposerCompactContext.Provider value={compact ? compactContext : null}>
|
|
127
127
|
<Primitive.form
|
|
128
128
|
{...rest}
|
|
129
|
+
data-aui-composer-type={composerType}
|
|
129
130
|
data-compact={isCompact ? "" : undefined}
|
|
130
131
|
ref={forwardedRef}
|
|
131
132
|
onSubmit={composeEventHandlers(onSubmit, handleSubmit)}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useAui, useAuiState } from "@assistant-ui/store";
|
|
4
4
|
import { useResource } from "@assistant-ui/tap";
|
|
5
|
-
import type {
|
|
5
|
+
import type { TriggerAdapter } from "@assistant-ui/core";
|
|
6
6
|
import {
|
|
7
7
|
createContext,
|
|
8
8
|
forwardRef,
|
|
@@ -74,7 +74,7 @@ export namespace ComposerPrimitiveTriggerPopover {
|
|
|
74
74
|
/** Overrides trigger detection for both textarea and Lexical inputs. `endOffset` is the exclusive replace bound. */
|
|
75
75
|
readonly matcher?: TriggerMatcher | undefined;
|
|
76
76
|
/** Adapter providing categories and items. */
|
|
77
|
-
readonly adapter?:
|
|
77
|
+
readonly adapter?: TriggerAdapter | undefined;
|
|
78
78
|
/** Whether the adapter is resolving items, surfaced to the popover scope for async sources. @default false */
|
|
79
79
|
readonly isLoading?: boolean | undefined;
|
|
80
80
|
};
|
|
@@ -89,22 +89,22 @@ export namespace ComposerPrimitiveTriggerPopover {
|
|
|
89
89
|
* `<TriggerPopover.Directive>` or `<TriggerPopover.Action>` as a child. Without
|
|
90
90
|
* a behavior the trigger stays closed.
|
|
91
91
|
*
|
|
92
|
-
* Must be placed inside `ComposerPrimitive.
|
|
92
|
+
* Must be placed inside `ComposerPrimitive.TriggerPopoverRoot`.
|
|
93
93
|
*
|
|
94
94
|
* @example
|
|
95
95
|
* ```tsx
|
|
96
|
-
* <ComposerPrimitive.
|
|
96
|
+
* <ComposerPrimitive.TriggerPopover
|
|
97
97
|
* char="@"
|
|
98
98
|
* adapter={mentionAdapter}
|
|
99
99
|
* >
|
|
100
|
-
* <ComposerPrimitive.
|
|
101
|
-
* <ComposerPrimitive.
|
|
100
|
+
* <ComposerPrimitive.TriggerPopover.Directive formatter={formatter} />
|
|
101
|
+
* <ComposerPrimitive.TriggerPopoverCategories>
|
|
102
102
|
* {(cats) => cats.map(...)}
|
|
103
|
-
* </ComposerPrimitive.
|
|
104
|
-
* <ComposerPrimitive.
|
|
103
|
+
* </ComposerPrimitive.TriggerPopoverCategories>
|
|
104
|
+
* <ComposerPrimitive.TriggerPopoverItems>
|
|
105
105
|
* {(items) => items.map(...)}
|
|
106
|
-
* </ComposerPrimitive.
|
|
107
|
-
* </ComposerPrimitive.
|
|
106
|
+
* </ComposerPrimitive.TriggerPopoverItems>
|
|
107
|
+
* </ComposerPrimitive.TriggerPopover>
|
|
108
108
|
* ```
|
|
109
109
|
*/
|
|
110
110
|
export const ComposerPrimitiveTriggerPopover = forwardRef<
|
|
@@ -196,7 +196,7 @@ export const ComposerPrimitiveTriggerPopover = forwardRef<
|
|
|
196
196
|
...(behavior ? { behavior } : {}),
|
|
197
197
|
resource: getResource(),
|
|
198
198
|
});
|
|
199
|
-
}, [root, char, hasMatcher, behavior]);
|
|
199
|
+
}, [root, char, hasMatcher, registeredMatcher, behavior]);
|
|
200
200
|
|
|
201
201
|
const pluginRegistry = useComposerInputPluginRegistryOptional();
|
|
202
202
|
useEffect(() => {
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
Unstable_TriggerItem,
|
|
6
|
-
} from "@assistant-ui/core";
|
|
7
|
-
import { unstable_defaultDirectiveFormatter } from "@assistant-ui/core";
|
|
3
|
+
import type { DirectiveFormatter, TriggerItem } from "@assistant-ui/core";
|
|
4
|
+
import { defaultDirectiveFormatter } from "@assistant-ui/core";
|
|
8
5
|
import { useEffect, useInsertionEffect, useRef, type FC } from "react";
|
|
9
6
|
import { useTriggerBehaviorRegistration } from "./TriggerPopover";
|
|
10
7
|
import type { TriggerBehavior } from "./triggerSelectionResource";
|
|
11
8
|
|
|
12
9
|
export namespace ComposerPrimitiveTriggerPopoverAction {
|
|
13
10
|
export type Props = {
|
|
14
|
-
/** Defaults to `
|
|
15
|
-
readonly formatter?:
|
|
11
|
+
/** Defaults to `defaultDirectiveFormatter`. */
|
|
12
|
+
readonly formatter?: DirectiveFormatter | undefined;
|
|
16
13
|
/** Fires the moment an item is selected; runs regardless of `removeOnExecute`. */
|
|
17
|
-
readonly onExecute: (item:
|
|
14
|
+
readonly onExecute: (item: TriggerItem) => void;
|
|
18
15
|
/** When true, strips the trigger text after executing. Defaults to `false` (keeps audit-trail chip). */
|
|
19
16
|
readonly removeOnExecute?: boolean | undefined;
|
|
20
17
|
};
|
|
@@ -25,16 +22,16 @@ export namespace ComposerPrimitiveTriggerPopoverAction {
|
|
|
25
22
|
* optionally leaving a directive chip behind as an audit trail. Render exactly
|
|
26
23
|
* one behavior sub-primitive per `<TriggerPopover>`.
|
|
27
24
|
*
|
|
28
|
-
* Exposed as `ComposerPrimitive.
|
|
25
|
+
* Exposed as `ComposerPrimitive.TriggerPopover.Action`.
|
|
29
26
|
*
|
|
30
27
|
* @example
|
|
31
28
|
* ```tsx
|
|
32
|
-
* <ComposerPrimitive.
|
|
33
|
-
* <ComposerPrimitive.
|
|
29
|
+
* <ComposerPrimitive.TriggerPopover char="/" adapter={slashAdapter}>
|
|
30
|
+
* <ComposerPrimitive.TriggerPopover.Action
|
|
34
31
|
* onExecute={(item) => commandHandlers[item.id]?.()}
|
|
35
32
|
* removeOnExecute={false}
|
|
36
33
|
* />
|
|
37
|
-
* </ComposerPrimitive.
|
|
34
|
+
* </ComposerPrimitive.TriggerPopover>
|
|
38
35
|
* ```
|
|
39
36
|
*/
|
|
40
37
|
export const ComposerPrimitiveTriggerPopoverAction: FC<
|
|
@@ -49,7 +46,7 @@ export const ComposerPrimitiveTriggerPopoverAction: FC<
|
|
|
49
46
|
useEffect(() => {
|
|
50
47
|
const behavior: TriggerBehavior = {
|
|
51
48
|
kind: "action",
|
|
52
|
-
formatter: formatter ??
|
|
49
|
+
formatter: formatter ?? defaultDirectiveFormatter,
|
|
53
50
|
onExecute: (item) => onExecuteRef.current(item),
|
|
54
51
|
...(removeOnExecute !== undefined ? { removeOnExecute } : {}),
|
|
55
52
|
};
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "react";
|
|
11
11
|
import { composeEventHandlers } from "radix-ui/internal";
|
|
12
12
|
import { useTriggerPopoverScopeContext } from "./TriggerPopover";
|
|
13
|
-
import type {
|
|
13
|
+
import type { TriggerCategory } from "@assistant-ui/core";
|
|
14
14
|
|
|
15
15
|
export namespace ComposerPrimitiveTriggerPopoverCategories {
|
|
16
16
|
export type Element = ComponentRef<typeof Primitive.div>;
|
|
@@ -18,7 +18,7 @@ export namespace ComposerPrimitiveTriggerPopoverCategories {
|
|
|
18
18
|
ComponentPropsWithoutRef<typeof Primitive.div>,
|
|
19
19
|
"children"
|
|
20
20
|
> & {
|
|
21
|
-
children: (categories: readonly
|
|
21
|
+
children: (categories: readonly TriggerCategory[]) => ReactNode;
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
Unstable_TriggerItem,
|
|
6
|
-
} from "@assistant-ui/core";
|
|
7
|
-
import { unstable_defaultDirectiveFormatter } from "@assistant-ui/core";
|
|
3
|
+
import type { DirectiveFormatter, TriggerItem } from "@assistant-ui/core";
|
|
4
|
+
import { defaultDirectiveFormatter } from "@assistant-ui/core";
|
|
8
5
|
import { useEffect, useInsertionEffect, useRef, type FC } from "react";
|
|
9
6
|
import { useTriggerBehaviorRegistration } from "./TriggerPopover";
|
|
10
7
|
import type { TriggerBehavior } from "./triggerSelectionResource";
|
|
11
8
|
|
|
12
9
|
export namespace ComposerPrimitiveTriggerPopoverDirective {
|
|
13
10
|
export type Props = {
|
|
14
|
-
/** Defaults to `
|
|
15
|
-
readonly formatter?:
|
|
11
|
+
/** Defaults to `defaultDirectiveFormatter`. */
|
|
12
|
+
readonly formatter?: DirectiveFormatter | undefined;
|
|
16
13
|
/** Fires after an item has been inserted into the composer. */
|
|
17
|
-
readonly onInserted?: ((item:
|
|
14
|
+
readonly onInserted?: ((item: TriggerItem) => void) | undefined;
|
|
18
15
|
};
|
|
19
16
|
}
|
|
20
17
|
|
|
@@ -22,16 +19,16 @@ export namespace ComposerPrimitiveTriggerPopoverDirective {
|
|
|
22
19
|
* Configures a `<TriggerPopover>` to insert a directive chip when an item is
|
|
23
20
|
* selected. Render exactly one behavior sub-primitive per `<TriggerPopover>`.
|
|
24
21
|
*
|
|
25
|
-
* Exposed as `ComposerPrimitive.
|
|
22
|
+
* Exposed as `ComposerPrimitive.TriggerPopover.Directive`.
|
|
26
23
|
*
|
|
27
24
|
* @example
|
|
28
25
|
* ```tsx
|
|
29
|
-
* <ComposerPrimitive.
|
|
30
|
-
* <ComposerPrimitive.
|
|
31
|
-
* formatter={
|
|
26
|
+
* <ComposerPrimitive.TriggerPopover char="@" adapter={mentionAdapter}>
|
|
27
|
+
* <ComposerPrimitive.TriggerPopover.Directive
|
|
28
|
+
* formatter={defaultDirectiveFormatter}
|
|
32
29
|
* onInserted={(item) => track("mention", item.id)}
|
|
33
30
|
* />
|
|
34
|
-
* </ComposerPrimitive.
|
|
31
|
+
* </ComposerPrimitive.TriggerPopover>
|
|
35
32
|
* ```
|
|
36
33
|
*/
|
|
37
34
|
export const ComposerPrimitiveTriggerPopoverDirective: FC<
|
|
@@ -46,7 +43,7 @@ export const ComposerPrimitiveTriggerPopoverDirective: FC<
|
|
|
46
43
|
useEffect(() => {
|
|
47
44
|
const behavior: TriggerBehavior = {
|
|
48
45
|
kind: "directive",
|
|
49
|
-
formatter: formatter ??
|
|
46
|
+
formatter: formatter ?? defaultDirectiveFormatter,
|
|
50
47
|
onInserted: (item) => onInsertedRef.current?.(item),
|
|
51
48
|
};
|
|
52
49
|
return register(behavior);
|