@assistant-ui/react 0.14.23 → 0.14.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/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/legacy-runtime/hooks/AttachmentContext.d.ts +12 -12
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts +3 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +2 -2
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +6 -2
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +5 -14
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/useComposerInputState.d.ts +13 -0
- package/dist/primitives/composer/useComposerInputState.d.ts.map +1 -0
- package/dist/primitives/composer/useComposerInputState.js +47 -0
- package/dist/primitives/composer/useComposerInputState.js.map +1 -0
- package/dist/primitives/messagePart/MessagePartText.d.ts +2 -0
- package/dist/primitives/messagePart/MessagePartText.d.ts.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
- package/dist/primitives/thread/ThreadMessages.d.ts +2 -2
- package/dist/primitives/thread/ThreadMessages.js +2 -2
- package/dist/primitives/thread.d.ts +3 -3
- package/dist/primitives/thread.js +3 -2
- package/dist/primitives/thread.js.map +1 -1
- package/dist/primitives/threadList/ThreadListRoot.d.ts.map +1 -1
- package/dist/primitives/threadList/ThreadListRoot.js +10 -3
- package/dist/primitives/threadList/ThreadListRoot.js.map +1 -1
- package/dist/primitives/threadListFocusGroup.d.ts +26 -0
- package/dist/primitives/threadListFocusGroup.d.ts.map +1 -0
- package/dist/primitives/threadListFocusGroup.js +15 -0
- package/dist/primitives/threadListFocusGroup.js.map +1 -0
- package/dist/primitives/threadListItem/ThreadListItemRoot.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemRoot.js +45 -27
- package/dist/primitives/threadListItem/ThreadListItemRoot.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js +26 -2
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.d.ts.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js +63 -38
- package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.d.ts +20 -3
- package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.d.ts.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js +95 -14
- package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.d.ts.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js +38 -21
- package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js.map +1 -1
- package/dist/unstable/useComposerInput.d.ts +84 -0
- package/dist/unstable/useComposerInput.d.ts.map +1 -0
- package/dist/unstable/useComposerInput.js +78 -0
- package/dist/unstable/useComposerInput.js.map +1 -0
- package/dist/utils/hooks/useMediaQuery.js +2 -2
- package/dist/utils/hooks/useMediaQuery.js.map +1 -1
- package/dist/utils/smooth/useSmooth.d.ts +4 -0
- package/dist/utils/smooth/useSmooth.d.ts.map +1 -1
- package/dist/utils/smooth/useSmooth.js +7 -1
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/dist/utils/useToolArgsFieldStatus.d.ts +2 -2
- package/dist/utils/useToolArgsFieldStatus.d.ts.map +1 -1
- package/package.json +16 -14
- package/src/index.ts +39 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/commandQueue.ts +5 -2
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-scheduling.test.ts +22 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +12 -2
- package/src/primitives/composer/ComposerInput.tsx +9 -20
- package/src/primitives/composer/trigger/triggerKeyboardResource.test.ts +18 -22
- package/src/primitives/composer/useComposerInputState.ts +34 -0
- package/src/primitives/messagePart/MessagePartText.tsx +1 -0
- package/src/primitives/thread/ThreadMessages.tsx +1 -0
- package/src/primitives/thread.ts +1 -0
- package/src/primitives/threadList/ThreadListRoot.tsx +8 -1
- package/src/primitives/threadListFocusGroup.ts +24 -0
- package/src/primitives/threadListItem/ThreadListItemRoot.tsx +53 -5
- package/src/primitives/threadListItem/ThreadListItemTrigger.tsx +45 -0
- package/src/primitives/threadListItem/ThreadListKeyboardNav.test.tsx +141 -0
- package/src/primitives/threadListItemMore/ThreadListItemMoreContent.tsx +22 -1
- package/src/primitives/threadListItemMore/ThreadListItemMoreRoot.tsx +64 -3
- package/src/primitives/threadListItemMore/ThreadListItemMoreTrigger.tsx +29 -2
- package/src/tests/generative-ui.test.tsx +21 -1
- package/src/tests/threadMessageById.test.tsx +224 -0
- package/src/unstable/useComposerInput.test.tsx +187 -0
- package/src/unstable/useComposerInput.ts +126 -0
- package/src/utils/hooks/useMediaQuery.ts +3 -2
- package/src/utils/smooth/useSmooth.test.tsx +26 -0
- package/src/utils/smooth/useSmooth.ts +7 -1
- package/src/primitives/threadListItem/ThreadListItemTrigger.ts +0 -23
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
//#region src/utils/useToolArgsFieldStatus.d.ts
|
|
2
2
|
declare const useToolArgsFieldStatus: (fieldPath: (string | number)[]) => {
|
|
3
|
+
type: string;
|
|
4
|
+
} | {
|
|
3
5
|
readonly type: "running";
|
|
4
6
|
} | {
|
|
5
7
|
readonly type: "complete";
|
|
@@ -7,8 +9,6 @@ declare const useToolArgsFieldStatus: (fieldPath: (string | number)[]) => {
|
|
|
7
9
|
readonly type: "incomplete";
|
|
8
10
|
readonly reason: "cancelled" | "length" | "content-filter" | "other" | "error";
|
|
9
11
|
readonly error?: unknown;
|
|
10
|
-
} | {
|
|
11
|
-
type: string;
|
|
12
12
|
};
|
|
13
13
|
//#endregion
|
|
14
14
|
export { useToolArgsFieldStatus };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useToolArgsFieldStatus.d.ts","names":[],"sources":["../../src/utils/useToolArgsFieldStatus.ts"],"mappings":";cAKa,sBAAA,GAA0B,SAAA
|
|
1
|
+
{"version":3,"file":"useToolArgsFieldStatus.d.ts","names":[],"sources":["../../src/utils/useToolArgsFieldStatus.ts"],"mappings":";cAKa,sBAAA,GAA0B,SAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.26",
|
|
4
4
|
"description": "Open-source TypeScript/React library for building production-grade AI chat experiences",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -55,21 +55,23 @@
|
|
|
55
55
|
],
|
|
56
56
|
"sideEffects": false,
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@assistant-ui/core": "^0.2.
|
|
59
|
-
"@assistant-ui/store": "^0.2.
|
|
60
|
-
"@assistant-ui/tap": "^0.9.
|
|
58
|
+
"@assistant-ui/core": "^0.2.20",
|
|
59
|
+
"@assistant-ui/store": "^0.2.19",
|
|
60
|
+
"@assistant-ui/tap": "^0.9.3",
|
|
61
61
|
"@radix-ui/primitive": "^1.1.4",
|
|
62
|
+
"@radix-ui/react-collection": "^1.1.10",
|
|
62
63
|
"@radix-ui/react-compose-refs": "^1.1.3",
|
|
63
64
|
"@radix-ui/react-context": "^1.1.4",
|
|
64
|
-
"@radix-ui/react-primitive": "^2.1.
|
|
65
|
+
"@radix-ui/react-primitive": "^2.1.6",
|
|
65
66
|
"@radix-ui/react-use-callback-ref": "^1.1.2",
|
|
67
|
+
"@radix-ui/react-use-controllable-state": "^1.2.3",
|
|
66
68
|
"@radix-ui/react-use-escape-keydown": "^1.1.2",
|
|
67
|
-
"assistant-cloud": "^0.1.
|
|
68
|
-
"assistant-stream": "^0.3.
|
|
69
|
-
"nanoid": "^5.1.
|
|
70
|
-
"radix-ui": "^1.
|
|
69
|
+
"assistant-cloud": "^0.1.34",
|
|
70
|
+
"assistant-stream": "^0.3.25",
|
|
71
|
+
"nanoid": "^5.1.15",
|
|
72
|
+
"radix-ui": "^1.6.0",
|
|
71
73
|
"react-textarea-autosize": "^8.5.9",
|
|
72
|
-
"safe-content-frame": "^0.0.
|
|
74
|
+
"safe-content-frame": "^0.0.22",
|
|
73
75
|
"zod": "^4.4.3",
|
|
74
76
|
"zustand": "^5.0.14"
|
|
75
77
|
},
|
|
@@ -90,15 +92,15 @@
|
|
|
90
92
|
"devDependencies": {
|
|
91
93
|
"@testing-library/react": "^16.3.2",
|
|
92
94
|
"@types/json-schema": "^7.0.15",
|
|
93
|
-
"@types/node": "^
|
|
95
|
+
"@types/node": "^26.0.0",
|
|
94
96
|
"@types/react": "^19.2.17",
|
|
95
97
|
"@types/react-dom": "^19.2.3",
|
|
96
98
|
"jsdom": "^29.1.1",
|
|
97
99
|
"react": "^19.2.7",
|
|
98
100
|
"react-dom": "^19.2.7",
|
|
99
|
-
"vitest": "^4.1.
|
|
100
|
-
"@assistant-ui/vite": "0.0.
|
|
101
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
101
|
+
"vitest": "^4.1.9",
|
|
102
|
+
"@assistant-ui/vite": "0.0.8",
|
|
103
|
+
"@assistant-ui/x-buildutils": "0.0.17"
|
|
102
104
|
},
|
|
103
105
|
"publishConfig": {
|
|
104
106
|
"access": "public",
|
package/src/index.ts
CHANGED
|
@@ -136,6 +136,7 @@ export type {
|
|
|
136
136
|
ExternalStoreSharedOptions,
|
|
137
137
|
ExternalStoreThreadListAdapter,
|
|
138
138
|
ExternalStoreThreadData,
|
|
139
|
+
ExternalStoreBranchChange,
|
|
139
140
|
} from "@assistant-ui/core";
|
|
140
141
|
export {
|
|
141
142
|
createMessageQueue,
|
|
@@ -215,6 +216,26 @@ export {
|
|
|
215
216
|
useAssistantInteractable,
|
|
216
217
|
type AssistantInteractableProps,
|
|
217
218
|
useInteractableState,
|
|
219
|
+
unstable_Interactables,
|
|
220
|
+
unstable_useInteractable,
|
|
221
|
+
type Unstable_InteractableConfig,
|
|
222
|
+
type Unstable_InferInteractableState,
|
|
223
|
+
type Unstable_InteractableVersionInfo,
|
|
224
|
+
unstable_useInteractableState,
|
|
225
|
+
unstable_useInteractableVersions,
|
|
226
|
+
unstable_interactableTool,
|
|
227
|
+
type Unstable_InteractableToolConfig,
|
|
228
|
+
type Unstable_InteractableToolRenderProps,
|
|
229
|
+
type Unstable_InteractableStateSchema,
|
|
230
|
+
type Unstable_InteractablesState,
|
|
231
|
+
type Unstable_InteractableDefinition,
|
|
232
|
+
type Unstable_InteractableRegistration,
|
|
233
|
+
type Unstable_InteractablesMethods,
|
|
234
|
+
type Unstable_InteractablePersistedState,
|
|
235
|
+
type Unstable_InteractablePersistenceAdapter,
|
|
236
|
+
type Unstable_InteractablePersistenceStatus,
|
|
237
|
+
type Unstable_InteractablesClientSchema,
|
|
238
|
+
type Unstable_InteractablesConfig,
|
|
218
239
|
useToolArgsStatus,
|
|
219
240
|
type ToolArgsStatus,
|
|
220
241
|
} from "@assistant-ui/core/react";
|
|
@@ -228,6 +249,14 @@ export type {
|
|
|
228
249
|
|
|
229
250
|
export { mergeModelContexts } from "@assistant-ui/core";
|
|
230
251
|
|
|
252
|
+
export {
|
|
253
|
+
unstable_getInteractableSnapshots,
|
|
254
|
+
unstable_formatInteractableSnapshot,
|
|
255
|
+
unstable_getInteractableVersions,
|
|
256
|
+
type Unstable_InteractableSnapshotEntry,
|
|
257
|
+
type Unstable_InteractableVersion,
|
|
258
|
+
} from "@assistant-ui/core";
|
|
259
|
+
|
|
231
260
|
export type { Tool } from "assistant-stream";
|
|
232
261
|
|
|
233
262
|
export { tool } from "@assistant-ui/core";
|
|
@@ -280,6 +309,7 @@ export * as ThreadListItemMorePrimitive from "./primitives/threadListItemMore";
|
|
|
280
309
|
export * as SelectionToolbarPrimitive from "./primitives/selectionToolbar";
|
|
281
310
|
|
|
282
311
|
export { groupPartByType, type GroupByContext } from "@assistant-ui/core/react";
|
|
312
|
+
export { unstable_useThreadMessageIds } from "@assistant-ui/core/react";
|
|
283
313
|
export { useMessagePartText } from "./primitives/messagePart/useMessagePartText";
|
|
284
314
|
export { useMessagePartReasoning } from "./primitives/messagePart/useMessagePartReasoning";
|
|
285
315
|
export { useMessagePartSource } from "./primitives/messagePart/useMessagePartSource";
|
|
@@ -447,6 +477,15 @@ export {
|
|
|
447
477
|
type Unstable_ComposerInputHistory,
|
|
448
478
|
} from "./unstable/useComposerInputHistory";
|
|
449
479
|
|
|
480
|
+
// Unstable - headless composer input bridge (value/send without ComposerPrimitive.Input)
|
|
481
|
+
export {
|
|
482
|
+
unstable_useComposerInput,
|
|
483
|
+
unstable_useTriggerPopoverAriaProps,
|
|
484
|
+
type Unstable_UseComposerInputOptions,
|
|
485
|
+
type Unstable_ComposerInput,
|
|
486
|
+
type Unstable_TriggerPopoverAriaProps,
|
|
487
|
+
} from "./unstable/useComposerInput";
|
|
488
|
+
|
|
450
489
|
export type { Assistant } from "./augmentations";
|
|
451
490
|
|
|
452
491
|
// --- mcp-apps ---
|
|
@@ -19,14 +19,17 @@ export const useCommandQueue = (opts: { onQueue: () => void }) => {
|
|
|
19
19
|
const [, rerender] = useState(0);
|
|
20
20
|
const queueStateRef = useRef<CommandQueueState>(createInitialQueueState());
|
|
21
21
|
|
|
22
|
-
const enqueue = (
|
|
22
|
+
const enqueue = (
|
|
23
|
+
command: AssistantTransportCommand,
|
|
24
|
+
options?: { schedule?: boolean },
|
|
25
|
+
) => {
|
|
23
26
|
queueStateRef.current = {
|
|
24
27
|
queued: [...queueStateRef.current.queued, command],
|
|
25
28
|
inTransit: queueStateRef.current.inTransit,
|
|
26
29
|
};
|
|
27
30
|
rerender((prev) => prev + 1);
|
|
28
31
|
|
|
29
|
-
onQueueRef.current();
|
|
32
|
+
if (options?.schedule !== false) onQueueRef.current();
|
|
30
33
|
};
|
|
31
34
|
|
|
32
35
|
const flush = (): QueuedCommand[] => {
|
|
@@ -99,6 +99,28 @@ describe("assistant transport scheduling contracts", () => {
|
|
|
99
99
|
expect(result.current.runBatchesRef.current[1]).toHaveLength(1);
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
+
it("can enqueue without scheduling until a run is started", async () => {
|
|
103
|
+
const { result } = renderHook(() => useTransportSchedulingHarness());
|
|
104
|
+
|
|
105
|
+
act(() => {
|
|
106
|
+
result.current.commandQueue.enqueue(createMessageCommand("staged"), {
|
|
107
|
+
schedule: false,
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
await Promise.resolve();
|
|
112
|
+
expect(result.current.runBatchesRef.current).toHaveLength(0);
|
|
113
|
+
|
|
114
|
+
act(() => {
|
|
115
|
+
result.current.runManager.schedule();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await waitFor(() => {
|
|
119
|
+
expect(result.current.runBatchesRef.current).toHaveLength(1);
|
|
120
|
+
});
|
|
121
|
+
expect(result.current.runBatchesRef.current[0]).toHaveLength(1);
|
|
122
|
+
});
|
|
123
|
+
|
|
102
124
|
it("cancel returns combined in-flight and queued commands", async () => {
|
|
103
125
|
const onCancel = vi.fn();
|
|
104
126
|
const { result } = renderHook(() =>
|
package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts
CHANGED
|
@@ -316,13 +316,23 @@ const useAssistantTransportThreadRuntime = <T>(
|
|
|
316
316
|
onNew: async (message: AppendMessage): Promise<void> => {
|
|
317
317
|
parentIdRef.current = message.parentId;
|
|
318
318
|
const command = convertAppendMessageToCommand(message);
|
|
319
|
-
commandQueue.enqueue(command
|
|
319
|
+
commandQueue.enqueue(command, {
|
|
320
|
+
schedule: message.startRun ?? message.role === "user",
|
|
321
|
+
});
|
|
320
322
|
},
|
|
321
323
|
...(options.capabilities?.edit && {
|
|
322
324
|
onEdit: async (message: AppendMessage): Promise<void> => {
|
|
323
325
|
parentIdRef.current = message.parentId;
|
|
324
326
|
const command = convertAppendMessageToCommand(message);
|
|
325
|
-
commandQueue.enqueue(command
|
|
327
|
+
commandQueue.enqueue(command, {
|
|
328
|
+
schedule: message.startRun ?? message.role === "user",
|
|
329
|
+
});
|
|
330
|
+
},
|
|
331
|
+
}),
|
|
332
|
+
...(commandQueue.state.queued.length > 0 && {
|
|
333
|
+
onReload: async (parentId: string | null) => {
|
|
334
|
+
parentIdRef.current = parentId;
|
|
335
|
+
runManager.schedule();
|
|
326
336
|
},
|
|
327
337
|
}),
|
|
328
338
|
onCancel: async () => {
|
|
@@ -21,10 +21,14 @@ import TextareaAutosize, {
|
|
|
21
21
|
import { useEscapeKeydown } from "@radix-ui/react-use-escape-keydown";
|
|
22
22
|
import { useOnScrollToBottom } from "../../utils/hooks/useOnScrollToBottom";
|
|
23
23
|
import { useMediaQuery } from "../../utils/hooks/useMediaQuery";
|
|
24
|
-
import {
|
|
24
|
+
import { useAui } from "@assistant-ui/store";
|
|
25
25
|
import { flushTapSync } from "@assistant-ui/tap";
|
|
26
26
|
import { useComposerInputPluginRegistryOptional } from "./ComposerInputPluginContext";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
useComposerInputDisabled,
|
|
29
|
+
useComposerInputValue,
|
|
30
|
+
useTriggerPopoverAriaProps,
|
|
31
|
+
} from "./useComposerInputState";
|
|
28
32
|
|
|
29
33
|
const TOUCH_PRIMARY_QUERY = "(pointer: coarse) and (not (any-pointer: fine))";
|
|
30
34
|
|
|
@@ -167,7 +171,6 @@ export const ComposerPrimitiveInput = forwardRef<
|
|
|
167
171
|
) => {
|
|
168
172
|
const aui = useAui();
|
|
169
173
|
const pluginRegistry = useComposerInputPluginRegistryOptional();
|
|
170
|
-
const activeAria = useTriggerPopoverActiveAriaOptional();
|
|
171
174
|
|
|
172
175
|
const declaredSubmitMode =
|
|
173
176
|
submitMode ?? (submitOnEnter === false ? "none" : "enter");
|
|
@@ -181,15 +184,8 @@ export const ComposerPrimitiveInput = forwardRef<
|
|
|
181
184
|
? "none"
|
|
182
185
|
: declaredSubmitMode;
|
|
183
186
|
|
|
184
|
-
const value =
|
|
185
|
-
|
|
186
|
-
return s.composer.text;
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
const isDisabled =
|
|
190
|
-
useAuiState(
|
|
191
|
-
(s) => s.thread.isDisabled || s.composer.dictation?.inputDisabled,
|
|
192
|
-
) || disabledProp;
|
|
187
|
+
const value = useComposerInputValue();
|
|
188
|
+
const isDisabled = useComposerInputDisabled(disabledProp);
|
|
193
189
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
194
190
|
const ref = useComposedRefs(forwardedRef, textareaRef);
|
|
195
191
|
// suppress text/cursor broadcasts during IME composition
|
|
@@ -322,14 +318,7 @@ export const ComposerPrimitiveInput = forwardRef<
|
|
|
322
318
|
return aui.on("threadListItem.switchedTo", focus);
|
|
323
319
|
}, [unstable_focusOnThreadSwitched, focus, aui]);
|
|
324
320
|
|
|
325
|
-
const ariaComboboxProps =
|
|
326
|
-
? {
|
|
327
|
-
"aria-controls": activeAria.popoverId,
|
|
328
|
-
"aria-expanded": true as const,
|
|
329
|
-
"aria-haspopup": "listbox" as const,
|
|
330
|
-
"aria-activedescendant": activeAria.highlightedItemId,
|
|
331
|
-
}
|
|
332
|
-
: {};
|
|
321
|
+
const ariaComboboxProps = useTriggerPopoverAriaProps();
|
|
333
322
|
|
|
334
323
|
const inputProps = {
|
|
335
324
|
name: "input" as const,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { createTapRoot, useResource } from "@assistant-ui/tap";
|
|
2
|
+
import { createTapRoot, flushTapSync, useResource } from "@assistant-ui/tap";
|
|
3
3
|
import type {
|
|
4
4
|
Unstable_TriggerCategory,
|
|
5
5
|
Unstable_TriggerItem,
|
|
@@ -23,8 +23,6 @@ const makeKeyEvent = (key: string, shiftKey = false) => ({
|
|
|
23
23
|
preventDefault: vi.fn(),
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
const tick = () => new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
27
|
-
|
|
28
26
|
const render = (
|
|
29
27
|
overrides: Partial<Parameters<typeof TriggerKeyboardResource>[0]> = {},
|
|
30
28
|
) => {
|
|
@@ -106,55 +104,53 @@ describe("TriggerKeyboardResource", () => {
|
|
|
106
104
|
expect(props.selectItem).not.toHaveBeenCalled();
|
|
107
105
|
});
|
|
108
106
|
|
|
109
|
-
it("moves the highlight forward on ArrowDown",
|
|
107
|
+
it("moves the highlight forward on ArrowDown", () => {
|
|
110
108
|
const { sub } = render();
|
|
111
109
|
|
|
112
|
-
const consumed =
|
|
113
|
-
|
|
110
|
+
const consumed = flushTapSync(() =>
|
|
111
|
+
sub.getValue().handleKeyDown(makeKeyEvent("ArrowDown")),
|
|
112
|
+
);
|
|
114
113
|
|
|
115
114
|
expect(consumed).toBe(true);
|
|
116
115
|
expect(sub.getValue().highlightedIndex).toBe(1);
|
|
117
116
|
});
|
|
118
117
|
|
|
119
|
-
it("wraps the highlight to the top on ArrowDown past the last entry",
|
|
118
|
+
it("wraps the highlight to the top on ArrowDown past the last entry", () => {
|
|
120
119
|
const { sub } = render();
|
|
121
120
|
const handle = sub.getValue().handleKeyDown;
|
|
122
121
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
flushTapSync(() => {
|
|
123
|
+
handle(makeKeyEvent("ArrowDown"));
|
|
124
|
+
handle(makeKeyEvent("ArrowDown"));
|
|
125
|
+
handle(makeKeyEvent("ArrowDown"));
|
|
126
|
+
});
|
|
127
127
|
|
|
128
128
|
expect(sub.getValue().highlightedIndex).toBe(0);
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
-
it("moves the highlight backward on ArrowUp",
|
|
131
|
+
it("moves the highlight backward on ArrowUp", () => {
|
|
132
132
|
const { sub } = render();
|
|
133
133
|
const handle = sub.getValue().handleKeyDown;
|
|
134
134
|
|
|
135
|
-
handle(makeKeyEvent("ArrowDown"));
|
|
136
|
-
|
|
137
|
-
handle(makeKeyEvent("ArrowUp"));
|
|
138
|
-
await tick();
|
|
135
|
+
flushTapSync(() => handle(makeKeyEvent("ArrowDown")));
|
|
136
|
+
flushTapSync(() => handle(makeKeyEvent("ArrowUp")));
|
|
139
137
|
|
|
140
138
|
expect(sub.getValue().highlightedIndex).toBe(0);
|
|
141
139
|
});
|
|
142
140
|
|
|
143
|
-
it("wraps the highlight to the bottom on ArrowUp from the first entry",
|
|
141
|
+
it("wraps the highlight to the bottom on ArrowUp from the first entry", () => {
|
|
144
142
|
const { sub } = render();
|
|
145
143
|
|
|
146
|
-
sub.getValue().handleKeyDown(makeKeyEvent("ArrowUp"));
|
|
147
|
-
await tick();
|
|
144
|
+
flushTapSync(() => sub.getValue().handleKeyDown(makeKeyEvent("ArrowUp")));
|
|
148
145
|
|
|
149
146
|
expect(sub.getValue().highlightedIndex).toBe(2);
|
|
150
147
|
});
|
|
151
148
|
|
|
152
|
-
it("keeps the highlight at 0 on ArrowDown when navigableList is empty",
|
|
149
|
+
it("keeps the highlight at 0 on ArrowDown when navigableList is empty", () => {
|
|
153
150
|
const { sub } = render({ navigableList: [] });
|
|
154
151
|
const e = makeKeyEvent("ArrowDown");
|
|
155
152
|
|
|
156
|
-
const consumed = sub.getValue().handleKeyDown(e);
|
|
157
|
-
await tick();
|
|
153
|
+
const consumed = flushTapSync(() => sub.getValue().handleKeyDown(e));
|
|
158
154
|
|
|
159
155
|
expect(consumed).toBe(true);
|
|
160
156
|
expect(e.preventDefault).toHaveBeenCalled();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useAuiState } from "@assistant-ui/store";
|
|
4
|
+
import { useTriggerPopoverActiveAriaOptional } from "./trigger/TriggerPopoverRootContext";
|
|
5
|
+
|
|
6
|
+
export type TriggerPopoverAriaProps = {
|
|
7
|
+
"aria-controls"?: string;
|
|
8
|
+
"aria-expanded"?: true;
|
|
9
|
+
"aria-haspopup"?: "listbox";
|
|
10
|
+
"aria-activedescendant"?: string | undefined;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function useComposerInputValue() {
|
|
14
|
+
return useAuiState((s) => (s.composer.isEditing ? s.composer.text : ""));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useComposerInputDisabled(disabled?: boolean | undefined) {
|
|
18
|
+
const composerDisabled = useAuiState(
|
|
19
|
+
(s) => s.thread.isDisabled || s.composer.dictation?.inputDisabled,
|
|
20
|
+
);
|
|
21
|
+
return Boolean(composerDisabled) || Boolean(disabled);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useTriggerPopoverAriaProps(): TriggerPopoverAriaProps {
|
|
25
|
+
const activeAria = useTriggerPopoverActiveAriaOptional();
|
|
26
|
+
if (!activeAria) return {};
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
"aria-controls": activeAria.popoverId,
|
|
30
|
+
"aria-expanded": true,
|
|
31
|
+
"aria-haspopup": "listbox",
|
|
32
|
+
"aria-activedescendant": activeAria.highlightedItemId,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -20,6 +20,7 @@ export namespace MessagePartPrimitiveText {
|
|
|
20
20
|
* Whether to enable smooth text streaming animation.
|
|
21
21
|
* When enabled, text appears with a typing effect as it streams in.
|
|
22
22
|
* Pass a `SmoothOptions` object to tune the reveal rate.
|
|
23
|
+
* Auto-disables under `prefers-reduced-motion: reduce`.
|
|
23
24
|
* @default true
|
|
24
25
|
*/
|
|
25
26
|
smooth?: boolean | SmoothOptions;
|
package/src/primitives/thread.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { ThreadPrimitiveViewportProvider as ViewportProvider } from "../context/
|
|
|
6
6
|
export { ThreadPrimitiveViewportFooter as ViewportFooter } from "./thread/ThreadViewportFooter";
|
|
7
7
|
export { ThreadPrimitiveMessages as Messages } from "./thread/ThreadMessages";
|
|
8
8
|
export { ThreadPrimitiveMessageByIndex as MessageByIndex } from "./thread/ThreadMessages";
|
|
9
|
+
export { ThreadPrimitiveUnstable_MessageById as Unstable_MessageById } from "./thread/ThreadMessages";
|
|
9
10
|
export { ThreadPrimitiveScrollToBottom as ScrollToBottom } from "./thread/ThreadScrollToBottom";
|
|
10
11
|
export { ThreadPrimitiveSuggestion as Suggestion } from "./thread/ThreadSuggestion";
|
|
11
12
|
export {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type ComponentRef,
|
|
7
7
|
forwardRef,
|
|
8
8
|
} from "react";
|
|
9
|
+
import { ThreadListCollection } from "../threadListFocusGroup";
|
|
9
10
|
|
|
10
11
|
type PrimitiveDivProps = ComponentPropsWithoutRef<typeof Primitive.div>;
|
|
11
12
|
|
|
@@ -18,7 +19,13 @@ export const ThreadListPrimitiveRoot = forwardRef<
|
|
|
18
19
|
ThreadListPrimitiveRoot.Element,
|
|
19
20
|
ThreadListPrimitiveRoot.Props
|
|
20
21
|
>((props, ref) => {
|
|
21
|
-
return
|
|
22
|
+
return (
|
|
23
|
+
<ThreadListCollection.Provider scope={undefined}>
|
|
24
|
+
<ThreadListCollection.Slot scope={undefined}>
|
|
25
|
+
<Primitive.div {...props} ref={ref} />
|
|
26
|
+
</ThreadListCollection.Slot>
|
|
27
|
+
</ThreadListCollection.Provider>
|
|
28
|
+
);
|
|
22
29
|
});
|
|
23
30
|
|
|
24
31
|
ThreadListPrimitiveRoot.displayName = "ThreadListPrimitive.Root";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createCollection } from "@radix-ui/react-collection";
|
|
4
|
+
import { createContext, useContext, type RefObject } from "react";
|
|
5
|
+
|
|
6
|
+
const [collection, useThreadListCollection] =
|
|
7
|
+
createCollection<HTMLButtonElement>("ThreadList");
|
|
8
|
+
|
|
9
|
+
export const ThreadListCollection = collection;
|
|
10
|
+
export { useThreadListCollection };
|
|
11
|
+
|
|
12
|
+
type ThreadListItemFocus = {
|
|
13
|
+
triggerRef: RefObject<HTMLButtonElement | null>;
|
|
14
|
+
moreRef: RefObject<HTMLButtonElement | null>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const ThreadListItemFocusContext = createContext<ThreadListItemFocus | null>(
|
|
18
|
+
null,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export const ThreadListItemFocusProvider = ThreadListItemFocusContext.Provider;
|
|
22
|
+
|
|
23
|
+
export const useThreadListItemFocus = (): ThreadListItemFocus | null =>
|
|
24
|
+
useContext(ThreadListItemFocusContext);
|
|
@@ -5,8 +5,17 @@ import {
|
|
|
5
5
|
type ComponentRef,
|
|
6
6
|
forwardRef,
|
|
7
7
|
type ComponentPropsWithoutRef,
|
|
8
|
+
type KeyboardEvent,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
8
11
|
} from "react";
|
|
9
12
|
import { useAuiState } from "@assistant-ui/store";
|
|
13
|
+
import { composeEventHandlers } from "@radix-ui/primitive";
|
|
14
|
+
import { Direction } from "radix-ui";
|
|
15
|
+
import {
|
|
16
|
+
ThreadListItemFocusProvider,
|
|
17
|
+
useThreadListCollection,
|
|
18
|
+
} from "../threadListFocusGroup";
|
|
10
19
|
|
|
11
20
|
type PrimitiveDivProps = ComponentPropsWithoutRef<typeof Primitive.div>;
|
|
12
21
|
|
|
@@ -23,12 +32,51 @@ export const ThreadListItemPrimitiveRoot = forwardRef<
|
|
|
23
32
|
(s) => s.threads.mainThreadId === s.threadListItem.id,
|
|
24
33
|
);
|
|
25
34
|
|
|
35
|
+
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
36
|
+
const moreRef = useRef<HTMLButtonElement | null>(null);
|
|
37
|
+
const focus = useMemo(() => ({ triggerRef, moreRef }), []);
|
|
38
|
+
|
|
39
|
+
const getItems = useThreadListCollection(undefined);
|
|
40
|
+
const direction = Direction.useDirection();
|
|
41
|
+
|
|
42
|
+
const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
43
|
+
const forwardKey = direction === "rtl" ? "ArrowLeft" : "ArrowRight";
|
|
44
|
+
const backKey = direction === "rtl" ? "ArrowRight" : "ArrowLeft";
|
|
45
|
+
const trigger = triggerRef.current;
|
|
46
|
+
const more = moreRef.current;
|
|
47
|
+
|
|
48
|
+
if (trigger && event.target === trigger) {
|
|
49
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
50
|
+
const triggers = getItems()
|
|
51
|
+
.map((item) => item.ref.current)
|
|
52
|
+
.filter((node): node is HTMLButtonElement => node !== null);
|
|
53
|
+
const next =
|
|
54
|
+
triggers[
|
|
55
|
+
triggers.indexOf(trigger) + (event.key === "ArrowDown" ? 1 : -1)
|
|
56
|
+
];
|
|
57
|
+
if (next) {
|
|
58
|
+
next.focus();
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
}
|
|
61
|
+
} else if (event.key === forwardKey && more) {
|
|
62
|
+
more.focus();
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
}
|
|
65
|
+
} else if (more && event.target === more && event.key === backKey) {
|
|
66
|
+
trigger?.focus();
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
26
71
|
return (
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
72
|
+
<ThreadListItemFocusProvider value={focus}>
|
|
73
|
+
<Primitive.div
|
|
74
|
+
{...(isMain ? { "data-active": "true", "aria-current": "true" } : null)}
|
|
75
|
+
{...props}
|
|
76
|
+
onKeyDown={composeEventHandlers(props.onKeyDown, onKeyDown)}
|
|
77
|
+
ref={ref}
|
|
78
|
+
/>
|
|
79
|
+
</ThreadListItemFocusProvider>
|
|
32
80
|
);
|
|
33
81
|
});
|
|
34
82
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { forwardRef } from "react";
|
|
4
|
+
import { useComposedRefs } from "@radix-ui/react-compose-refs";
|
|
5
|
+
import {
|
|
6
|
+
type ActionButtonElement,
|
|
7
|
+
type ActionButtonProps,
|
|
8
|
+
createActionButton,
|
|
9
|
+
} from "../../utils/createActionButton";
|
|
10
|
+
import { useThreadListItemTrigger as useThreadListItemTriggerBehavior } from "@assistant-ui/core/react";
|
|
11
|
+
import {
|
|
12
|
+
ThreadListCollection,
|
|
13
|
+
useThreadListItemFocus,
|
|
14
|
+
} from "../threadListFocusGroup";
|
|
15
|
+
|
|
16
|
+
const useThreadListItemTrigger = () => {
|
|
17
|
+
const { switchTo } = useThreadListItemTriggerBehavior();
|
|
18
|
+
return switchTo;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export namespace ThreadListItemPrimitiveTrigger {
|
|
22
|
+
export type Element = ActionButtonElement;
|
|
23
|
+
export type Props = ActionButtonProps<typeof useThreadListItemTrigger>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ThreadListItemTriggerButton = createActionButton(
|
|
27
|
+
"ThreadListItemPrimitive.Trigger",
|
|
28
|
+
useThreadListItemTrigger,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
export const ThreadListItemPrimitiveTrigger = forwardRef<
|
|
32
|
+
ThreadListItemPrimitiveTrigger.Element,
|
|
33
|
+
ThreadListItemPrimitiveTrigger.Props
|
|
34
|
+
>((props, forwardedRef) => {
|
|
35
|
+
const focus = useThreadListItemFocus();
|
|
36
|
+
const ref = useComposedRefs(forwardedRef, focus?.triggerRef);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<ThreadListCollection.ItemSlot scope={undefined}>
|
|
40
|
+
<ThreadListItemTriggerButton {...props} ref={ref} />
|
|
41
|
+
</ThreadListCollection.ItemSlot>
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
ThreadListItemPrimitiveTrigger.displayName = "ThreadListItemPrimitive.Trigger";
|