@assistant-ui/react 0.12.22 → 0.12.24
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/client/ExternalThread.d.ts.map +1 -1
- package/dist/client/ExternalThread.js +1 -0
- package/dist/client/ExternalThread.js.map +1 -1
- package/dist/client/InMemoryThreadList.d.ts.map +1 -1
- package/dist/client/InMemoryThreadList.js +2 -0
- package/dist/client/InMemoryThreadList.js.map +1 -1
- package/dist/client/SingleThreadList.d.ts.map +1 -1
- package/dist/client/SingleThreadList.js +2 -0
- package/dist/client/SingleThreadList.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -0
- package/dist/internal.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +27 -12
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerInputPluginContext.d.ts +31 -0
- package/dist/primitives/composer/ComposerInputPluginContext.d.ts.map +1 -0
- package/dist/primitives/composer/ComposerInputPluginContext.js +32 -0
- package/dist/primitives/composer/ComposerInputPluginContext.js.map +1 -0
- package/dist/primitives/composer/mention/ComposerMentionContext.d.ts +4 -2
- package/dist/primitives/composer/mention/ComposerMentionContext.d.ts.map +1 -1
- package/dist/primitives/composer/mention/ComposerMentionContext.js +21 -13
- package/dist/primitives/composer/mention/ComposerMentionContext.js.map +1 -1
- package/dist/primitives/composer/mention/index.d.ts +4 -4
- package/dist/primitives/composer/mention/index.d.ts.map +1 -1
- package/dist/primitives/composer/mention/index.js +6 -4
- package/dist/primitives/composer/mention/index.js.map +1 -1
- package/dist/primitives/composer/slash-command/ComposerSlashCommandRoot.d.ts +36 -0
- package/dist/primitives/composer/slash-command/ComposerSlashCommandRoot.d.ts.map +1 -0
- package/dist/primitives/composer/slash-command/ComposerSlashCommandRoot.js +36 -0
- package/dist/primitives/composer/slash-command/ComposerSlashCommandRoot.js.map +1 -0
- package/dist/primitives/composer/slash-command/index.d.ts +2 -0
- package/dist/primitives/composer/slash-command/index.d.ts.map +1 -0
- package/dist/primitives/composer/slash-command/index.js +2 -0
- package/dist/primitives/composer/slash-command/index.js.map +1 -0
- package/dist/primitives/composer/{mention/ComposerMentionBack.d.ts → trigger/TriggerPopoverBack.d.ts} +3 -10
- package/dist/primitives/composer/trigger/TriggerPopoverBack.d.ts.map +1 -0
- package/dist/primitives/composer/trigger/TriggerPopoverBack.js +19 -0
- package/dist/primitives/composer/trigger/TriggerPopoverBack.js.map +1 -0
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.d.ts +38 -0
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.d.ts.map +1 -0
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.js +35 -0
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.js.map +1 -0
- package/dist/primitives/composer/trigger/TriggerPopoverContext.d.ts +37 -0
- package/dist/primitives/composer/trigger/TriggerPopoverContext.d.ts.map +1 -0
- package/dist/primitives/composer/trigger/TriggerPopoverContext.js +70 -0
- package/dist/primitives/composer/trigger/TriggerPopoverContext.js.map +1 -0
- package/dist/primitives/composer/trigger/TriggerPopoverItems.d.ts +40 -0
- package/dist/primitives/composer/trigger/TriggerPopoverItems.d.ts.map +1 -0
- package/dist/primitives/composer/trigger/TriggerPopoverItems.js +35 -0
- package/dist/primitives/composer/trigger/TriggerPopoverItems.js.map +1 -0
- package/dist/primitives/composer/trigger/TriggerPopoverPopover.d.ts +26 -0
- package/dist/primitives/composer/trigger/TriggerPopoverPopover.d.ts.map +1 -0
- package/dist/primitives/composer/trigger/TriggerPopoverPopover.js +28 -0
- package/dist/primitives/composer/trigger/TriggerPopoverPopover.js.map +1 -0
- package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts +53 -0
- package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -0
- package/dist/primitives/composer/{mention/MentionResource.js → trigger/TriggerPopoverResource.js} +50 -25
- package/dist/primitives/composer/trigger/TriggerPopoverResource.js.map +1 -0
- package/dist/primitives/composer/trigger/detectTrigger.d.ts +2 -0
- package/dist/primitives/composer/trigger/detectTrigger.d.ts.map +1 -0
- package/dist/primitives/composer/{mention/detectMentionTrigger.js → trigger/detectTrigger.js} +4 -4
- package/dist/primitives/composer/trigger/detectTrigger.js.map +1 -0
- package/dist/primitives/composer/trigger/index.d.ts +7 -0
- package/dist/primitives/composer/trigger/index.d.ts.map +1 -0
- package/dist/primitives/composer/trigger/index.js +6 -0
- package/dist/primitives/composer/trigger/index.js.map +1 -0
- package/dist/primitives/composer.d.ts +10 -0
- package/dist/primitives/composer.d.ts.map +1 -1
- package/dist/primitives/composer.js +14 -0
- package/dist/primitives/composer.js.map +1 -1
- package/dist/primitives/message/MessageRoot.d.ts +25 -3
- package/dist/primitives/message/MessageRoot.d.ts.map +1 -1
- package/dist/primitives/message/MessageRoot.js +2 -2
- package/dist/primitives/message/MessageRoot.js.map +1 -1
- package/dist/primitives/thread/ThreadViewportSlack.d.ts +2 -2
- package/dist/primitives/thread/ThreadViewportSlack.d.ts.map +1 -1
- package/dist/unstable/useSlashCommandAdapter.d.ts +34 -0
- package/dist/unstable/useSlashCommandAdapter.d.ts.map +1 -0
- package/dist/unstable/useSlashCommandAdapter.js +50 -0
- package/dist/unstable/useSlashCommandAdapter.js.map +1 -0
- package/package.json +7 -7
- package/src/client/ExternalThread.ts +1 -0
- package/src/client/InMemoryThreadList.ts +3 -0
- package/src/client/SingleThreadList.ts +2 -0
- package/src/index.ts +14 -0
- package/src/internal.ts +3 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.test.ts +186 -3
- package/src/primitives/composer/ComposerInput.tsx +25 -18
- package/src/primitives/composer/ComposerInputPluginContext.tsx +100 -0
- package/src/primitives/composer/mention/ComposerMentionContext.tsx +56 -22
- package/src/primitives/composer/mention/index.ts +11 -8
- package/src/primitives/composer/slash-command/ComposerSlashCommandRoot.tsx +76 -0
- package/src/primitives/composer/slash-command/index.ts +1 -0
- package/src/primitives/composer/trigger/TriggerPopoverBack.tsx +40 -0
- package/src/primitives/composer/{mention/ComposerMentionCategories.tsx → trigger/TriggerPopoverCategories.tsx} +33 -28
- package/src/primitives/composer/trigger/TriggerPopoverContext.tsx +129 -0
- package/src/primitives/composer/{mention/ComposerMentionItems.tsx → trigger/TriggerPopoverItems.tsx} +34 -29
- package/src/primitives/composer/trigger/TriggerPopoverPopover.tsx +51 -0
- package/src/primitives/composer/{mention/MentionResource.ts → trigger/TriggerPopoverResource.ts} +146 -98
- package/src/primitives/composer/{mention/detectMentionTrigger.test.ts → trigger/detectTrigger.test.ts} +15 -15
- package/src/primitives/composer/{mention/detectMentionTrigger.ts → trigger/detectTrigger.ts} +3 -3
- package/src/primitives/composer/trigger/index.ts +16 -0
- package/src/primitives/composer.ts +16 -0
- package/src/primitives/message/MessageRoot.tsx +18 -4
- package/src/primitives/thread/ThreadViewportSlack.tsx +2 -2
- package/src/tests/BaseComposerRuntimeCore.test.ts +33 -1
- package/src/unstable/useSlashCommandAdapter.ts +83 -0
- package/dist/primitives/composer/mention/ComposerMentionBack.d.ts.map +0 -1
- package/dist/primitives/composer/mention/ComposerMentionBack.js +0 -28
- package/dist/primitives/composer/mention/ComposerMentionBack.js.map +0 -1
- package/dist/primitives/composer/mention/ComposerMentionCategories.d.ts +0 -46
- package/dist/primitives/composer/mention/ComposerMentionCategories.d.ts.map +0 -1
- package/dist/primitives/composer/mention/ComposerMentionCategories.js +0 -32
- package/dist/primitives/composer/mention/ComposerMentionCategories.js.map +0 -1
- package/dist/primitives/composer/mention/ComposerMentionItems.d.ts +0 -50
- package/dist/primitives/composer/mention/ComposerMentionItems.d.ts.map +0 -1
- package/dist/primitives/composer/mention/ComposerMentionItems.js +0 -30
- package/dist/primitives/composer/mention/ComposerMentionItems.js.map +0 -1
- package/dist/primitives/composer/mention/ComposerMentionPopover.d.ts +0 -26
- package/dist/primitives/composer/mention/ComposerMentionPopover.d.ts.map +0 -1
- package/dist/primitives/composer/mention/ComposerMentionPopover.js +0 -28
- package/dist/primitives/composer/mention/ComposerMentionPopover.js.map +0 -1
- package/dist/primitives/composer/mention/MentionResource.d.ts +0 -39
- package/dist/primitives/composer/mention/MentionResource.d.ts.map +0 -1
- package/dist/primitives/composer/mention/MentionResource.js.map +0 -1
- package/dist/primitives/composer/mention/detectMentionTrigger.d.ts +0 -2
- package/dist/primitives/composer/mention/detectMentionTrigger.d.ts.map +0 -1
- package/dist/primitives/composer/mention/detectMentionTrigger.js.map +0 -1
- package/src/primitives/composer/mention/ComposerMentionBack.tsx +0 -55
- package/src/primitives/composer/mention/ComposerMentionPopover.tsx +0 -52
package/src/primitives/composer/{mention/MentionResource.ts → trigger/TriggerPopoverResource.ts}
RENAMED
|
@@ -7,43 +7,70 @@ import {
|
|
|
7
7
|
tapRef,
|
|
8
8
|
} from "@assistant-ui/tap";
|
|
9
9
|
import type {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
Unstable_TriggerAdapter,
|
|
11
|
+
Unstable_TriggerCategory,
|
|
12
|
+
Unstable_TriggerItem,
|
|
13
13
|
Unstable_DirectiveFormatter,
|
|
14
14
|
} from "@assistant-ui/core";
|
|
15
15
|
import type { AssistantClient } from "@assistant-ui/store";
|
|
16
|
-
import {
|
|
16
|
+
import { detectTrigger } from "./detectTrigger";
|
|
17
|
+
|
|
18
|
+
function isTriggerItem(
|
|
19
|
+
x: Unstable_TriggerItem | Unstable_TriggerCategory,
|
|
20
|
+
): x is Unstable_TriggerItem {
|
|
21
|
+
return "type" in x;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function matchesQuery(item: Unstable_TriggerItem, lower: string): boolean {
|
|
25
|
+
return (
|
|
26
|
+
item.id.toLowerCase().includes(lower) ||
|
|
27
|
+
item.label.toLowerCase().includes(lower) ||
|
|
28
|
+
(item.description?.toLowerCase().includes(lower) ?? false)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
17
31
|
|
|
18
32
|
// =============================================================================
|
|
19
33
|
// Types
|
|
20
34
|
// =============================================================================
|
|
21
35
|
|
|
22
|
-
export type
|
|
36
|
+
export type TriggerPopoverKeyEvent = {
|
|
23
37
|
readonly key: string;
|
|
24
38
|
readonly shiftKey: boolean;
|
|
25
39
|
preventDefault(): void;
|
|
26
40
|
};
|
|
27
41
|
|
|
28
|
-
export type SelectItemOverride = (item:
|
|
42
|
+
export type SelectItemOverride = (item: Unstable_TriggerItem) => boolean;
|
|
29
43
|
|
|
30
|
-
export type
|
|
44
|
+
export type OnSelectBehavior =
|
|
45
|
+
| {
|
|
46
|
+
type: "insertDirective";
|
|
47
|
+
formatter: Unstable_DirectiveFormatter;
|
|
48
|
+
}
|
|
49
|
+
| {
|
|
50
|
+
type: "action";
|
|
51
|
+
handler: (item: Unstable_TriggerItem) => void;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type TriggerPopoverResourceOutput = {
|
|
31
55
|
// State
|
|
32
56
|
readonly open: boolean;
|
|
33
57
|
readonly query: string;
|
|
34
58
|
readonly activeCategoryId: string | null;
|
|
35
|
-
readonly categories: readonly
|
|
36
|
-
readonly items: readonly
|
|
59
|
+
readonly categories: readonly Unstable_TriggerCategory[];
|
|
60
|
+
readonly items: readonly Unstable_TriggerItem[];
|
|
37
61
|
readonly highlightedIndex: number;
|
|
38
62
|
readonly isSearchMode: boolean;
|
|
39
|
-
|
|
63
|
+
/** Stable ID prefix for generating accessible element IDs. */
|
|
64
|
+
readonly popoverId: string;
|
|
65
|
+
/** ID of the currently highlighted item (for aria-activedescendant). */
|
|
66
|
+
readonly highlightedItemId: string | undefined;
|
|
40
67
|
|
|
41
68
|
// Actions
|
|
42
69
|
selectCategory(categoryId: string): void;
|
|
43
70
|
goBack(): void;
|
|
44
|
-
selectItem(item:
|
|
71
|
+
selectItem(item: Unstable_TriggerItem): void;
|
|
45
72
|
close(): void;
|
|
46
|
-
handleKeyDown(e:
|
|
73
|
+
handleKeyDown(e: TriggerPopoverKeyEvent): boolean;
|
|
47
74
|
|
|
48
75
|
// Internal (for ComposerInput integration)
|
|
49
76
|
setCursorPosition(pos: number): void;
|
|
@@ -54,20 +81,23 @@ export type MentionResourceOutput = {
|
|
|
54
81
|
// Resource
|
|
55
82
|
// =============================================================================
|
|
56
83
|
|
|
57
|
-
export const
|
|
84
|
+
export const TriggerPopoverResource = resource(
|
|
58
85
|
({
|
|
59
86
|
adapter,
|
|
60
87
|
text,
|
|
61
88
|
triggerChar,
|
|
62
|
-
|
|
89
|
+
onSelect,
|
|
63
90
|
aui,
|
|
91
|
+
popoverId,
|
|
64
92
|
}: {
|
|
65
|
-
adapter:
|
|
93
|
+
adapter: Unstable_TriggerAdapter | undefined;
|
|
66
94
|
text: string;
|
|
67
95
|
triggerChar: string;
|
|
68
|
-
|
|
96
|
+
onSelect: OnSelectBehavior;
|
|
69
97
|
aui: AssistantClient;
|
|
70
|
-
|
|
98
|
+
/** Stable ID for accessible element IDs (pass React's useId() from component layer). */
|
|
99
|
+
popoverId: string;
|
|
100
|
+
}): TriggerPopoverResourceOutput => {
|
|
71
101
|
// -------------------------------------------------------------------------
|
|
72
102
|
// Cursor tracking + trigger detection
|
|
73
103
|
// -------------------------------------------------------------------------
|
|
@@ -76,7 +106,7 @@ export const MentionResource = resource(
|
|
|
76
106
|
|
|
77
107
|
const trigger = tapMemo(() => {
|
|
78
108
|
const pos = Math.min(cursorPosition, text.length);
|
|
79
|
-
return
|
|
109
|
+
return detectTrigger(text, triggerChar, pos);
|
|
80
110
|
}, [cursorPosition, text, triggerChar]);
|
|
81
111
|
|
|
82
112
|
const open = trigger !== null && adapter !== undefined;
|
|
@@ -95,7 +125,7 @@ export const MentionResource = resource(
|
|
|
95
125
|
if (!open) setActiveCategoryId(null);
|
|
96
126
|
}, [open]);
|
|
97
127
|
|
|
98
|
-
const categories = tapMemo<readonly
|
|
128
|
+
const categories = tapMemo<readonly Unstable_TriggerCategory[]>(() => {
|
|
99
129
|
if (!open || !adapter) return [];
|
|
100
130
|
return adapter.categories();
|
|
101
131
|
}, [open, adapter]);
|
|
@@ -106,33 +136,31 @@ export const MentionResource = resource(
|
|
|
106
136
|
// Items + search
|
|
107
137
|
// -------------------------------------------------------------------------
|
|
108
138
|
|
|
109
|
-
const allItems = tapMemo<readonly
|
|
139
|
+
const allItems = tapMemo<readonly Unstable_TriggerItem[]>(() => {
|
|
110
140
|
if (!effectiveActiveCategoryId || !adapter) return [];
|
|
111
141
|
return adapter.categoryItems(effectiveActiveCategoryId);
|
|
112
142
|
}, [effectiveActiveCategoryId, adapter]);
|
|
113
143
|
|
|
114
144
|
const searchResults = tapMemo<
|
|
115
|
-
readonly
|
|
145
|
+
readonly Unstable_TriggerItem[] | null
|
|
116
146
|
>(() => {
|
|
117
|
-
if (!open || !adapter ||
|
|
147
|
+
if (!open || !adapter || effectiveActiveCategoryId) return null;
|
|
148
|
+
// If categories exist and query is empty, show categories first (not search)
|
|
149
|
+
if (!query && categories.length > 0) return null;
|
|
118
150
|
if (adapter.search) return adapter.search(query);
|
|
119
151
|
|
|
120
|
-
|
|
121
|
-
const all:
|
|
152
|
+
// Fallback: search all categories manually (reuse already-computed list)
|
|
153
|
+
const all: Unstable_TriggerItem[] = [];
|
|
122
154
|
const lower = query.toLowerCase();
|
|
123
|
-
for (const cat of
|
|
155
|
+
for (const cat of categories) {
|
|
124
156
|
for (const item of adapter.categoryItems(cat.id)) {
|
|
125
|
-
if (
|
|
126
|
-
item.id.toLowerCase().includes(lower) ||
|
|
127
|
-
item.label.toLowerCase().includes(lower) ||
|
|
128
|
-
item.description?.toLowerCase().includes(lower)
|
|
129
|
-
) {
|
|
157
|
+
if (matchesQuery(item, lower)) {
|
|
130
158
|
all.push(item);
|
|
131
159
|
}
|
|
132
160
|
}
|
|
133
161
|
}
|
|
134
162
|
return all;
|
|
135
|
-
}, [open, adapter, query, effectiveActiveCategoryId]);
|
|
163
|
+
}, [open, adapter, query, effectiveActiveCategoryId, categories]);
|
|
136
164
|
|
|
137
165
|
const isSearchMode = searchResults !== null;
|
|
138
166
|
|
|
@@ -153,12 +181,7 @@ export const MentionResource = resource(
|
|
|
153
181
|
if (isSearchMode) return searchResults ?? [];
|
|
154
182
|
if (!query) return allItems;
|
|
155
183
|
const lower = query.toLowerCase();
|
|
156
|
-
return allItems.filter(
|
|
157
|
-
(item) =>
|
|
158
|
-
item.id.toLowerCase().includes(lower) ||
|
|
159
|
-
item.label.toLowerCase().includes(lower) ||
|
|
160
|
-
item.description?.toLowerCase().includes(lower),
|
|
161
|
-
);
|
|
184
|
+
return allItems.filter((item) => matchesQuery(item, lower));
|
|
162
185
|
}, [allItems, query, isSearchMode, searchResults]);
|
|
163
186
|
|
|
164
187
|
// -------------------------------------------------------------------------
|
|
@@ -186,7 +209,7 @@ export const MentionResource = resource(
|
|
|
186
209
|
}, [navigableList]);
|
|
187
210
|
|
|
188
211
|
// -------------------------------------------------------------------------
|
|
189
|
-
//
|
|
212
|
+
// Select-item override (for Lexical integration)
|
|
190
213
|
// -------------------------------------------------------------------------
|
|
191
214
|
|
|
192
215
|
const selectItemOverrideRef = tapRef<SelectItemOverride | null>(null);
|
|
@@ -216,27 +239,42 @@ export const MentionResource = resource(
|
|
|
216
239
|
setHighlightedIndex(0);
|
|
217
240
|
});
|
|
218
241
|
|
|
219
|
-
const selectItem = tapEffectEvent((item:
|
|
242
|
+
const selectItem = tapEffectEvent((item: Unstable_TriggerItem) => {
|
|
220
243
|
if (!trigger) return;
|
|
221
244
|
|
|
222
|
-
// Try the
|
|
245
|
+
// Try the override first (e.g. Lexical MentionPlugin)
|
|
223
246
|
if (selectItemOverrideRef.current?.(item)) {
|
|
224
247
|
setActiveCategoryId(null);
|
|
225
248
|
setHighlightedIndex(0);
|
|
226
249
|
return;
|
|
227
250
|
}
|
|
228
251
|
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
252
|
+
// Behavior depends on the onSelect configuration
|
|
253
|
+
if (onSelect.type === "insertDirective") {
|
|
254
|
+
// Insert directive text (mention path)
|
|
255
|
+
const currentText = aui.composer().getState().text;
|
|
256
|
+
const before = currentText.slice(0, trigger.offset);
|
|
257
|
+
const after = currentText.slice(
|
|
258
|
+
trigger.offset + triggerChar.length + trigger.query.length,
|
|
259
|
+
);
|
|
260
|
+
const directive = onSelect.formatter.serialize(item);
|
|
261
|
+
const newText =
|
|
262
|
+
before + directive + (after.startsWith(" ") ? after : ` ${after}`);
|
|
263
|
+
|
|
264
|
+
aui.composer().setText(newText);
|
|
265
|
+
} else if (onSelect.type === "action") {
|
|
266
|
+
// Execute action + clear trigger text (slash command path)
|
|
267
|
+
const currentText = aui.composer().getState().text;
|
|
268
|
+
const before = currentText.slice(0, trigger.offset);
|
|
269
|
+
const after = currentText.slice(
|
|
270
|
+
trigger.offset + triggerChar.length + trigger.query.length,
|
|
271
|
+
);
|
|
272
|
+
const newText = before + after.trimStart();
|
|
273
|
+
aui.composer().setText(newText);
|
|
274
|
+
|
|
275
|
+
onSelect.handler(item);
|
|
276
|
+
}
|
|
238
277
|
|
|
239
|
-
aui.composer().setText(newText);
|
|
240
278
|
setActiveCategoryId(null);
|
|
241
279
|
setHighlightedIndex(0);
|
|
242
280
|
});
|
|
@@ -250,63 +288,72 @@ export const MentionResource = resource(
|
|
|
250
288
|
}
|
|
251
289
|
});
|
|
252
290
|
|
|
253
|
-
const handleKeyDown = tapEffectEvent(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
case "ArrowUp": {
|
|
267
|
-
e.preventDefault();
|
|
268
|
-
setHighlightedIndex((prev) => {
|
|
269
|
-
const len = navigableList.length;
|
|
270
|
-
if (len === 0) return 0;
|
|
271
|
-
return prev > 0 ? prev - 1 : len - 1;
|
|
272
|
-
});
|
|
273
|
-
return true;
|
|
274
|
-
}
|
|
275
|
-
case "Enter": {
|
|
276
|
-
if (e.shiftKey) return false;
|
|
277
|
-
e.preventDefault();
|
|
278
|
-
const item = navigableList[highlightedIndex];
|
|
279
|
-
if (!item) return true;
|
|
280
|
-
|
|
281
|
-
if (isSearchMode || effectiveActiveCategoryId) {
|
|
282
|
-
selectItem(item as Unstable_MentionItem);
|
|
283
|
-
} else {
|
|
284
|
-
selectCategory((item as Unstable_MentionCategory).id);
|
|
291
|
+
const handleKeyDown = tapEffectEvent(
|
|
292
|
+
(e: TriggerPopoverKeyEvent): boolean => {
|
|
293
|
+
if (!open) return false;
|
|
294
|
+
|
|
295
|
+
switch (e.key) {
|
|
296
|
+
case "ArrowDown": {
|
|
297
|
+
e.preventDefault();
|
|
298
|
+
setHighlightedIndex((prev) => {
|
|
299
|
+
const len = navigableList.length;
|
|
300
|
+
if (len === 0) return 0;
|
|
301
|
+
return prev < len - 1 ? prev + 1 : 0;
|
|
302
|
+
});
|
|
303
|
+
return true;
|
|
285
304
|
}
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
case "Escape": {
|
|
289
|
-
e.preventDefault();
|
|
290
|
-
close();
|
|
291
|
-
return true;
|
|
292
|
-
}
|
|
293
|
-
case "Backspace": {
|
|
294
|
-
if (effectiveActiveCategoryId && query === "") {
|
|
305
|
+
case "ArrowUp": {
|
|
295
306
|
e.preventDefault();
|
|
296
|
-
|
|
307
|
+
setHighlightedIndex((prev) => {
|
|
308
|
+
const len = navigableList.length;
|
|
309
|
+
if (len === 0) return 0;
|
|
310
|
+
return prev > 0 ? prev - 1 : len - 1;
|
|
311
|
+
});
|
|
297
312
|
return true;
|
|
298
313
|
}
|
|
299
|
-
|
|
314
|
+
case "Enter": {
|
|
315
|
+
if (e.shiftKey) return false;
|
|
316
|
+
e.preventDefault();
|
|
317
|
+
const item = navigableList[highlightedIndex];
|
|
318
|
+
if (!item) return true;
|
|
319
|
+
|
|
320
|
+
if (isTriggerItem(item)) {
|
|
321
|
+
selectItem(item);
|
|
322
|
+
} else {
|
|
323
|
+
selectCategory(item.id);
|
|
324
|
+
}
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
case "Escape": {
|
|
328
|
+
e.preventDefault();
|
|
329
|
+
close();
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
case "Backspace": {
|
|
333
|
+
if (effectiveActiveCategoryId && query === "") {
|
|
334
|
+
e.preventDefault();
|
|
335
|
+
goBack();
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
default:
|
|
341
|
+
return false;
|
|
300
342
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
});
|
|
343
|
+
},
|
|
344
|
+
);
|
|
305
345
|
|
|
306
346
|
// -------------------------------------------------------------------------
|
|
307
347
|
// Output
|
|
308
348
|
// -------------------------------------------------------------------------
|
|
309
349
|
|
|
350
|
+
// Compute highlighted item ID for aria-activedescendant
|
|
351
|
+
const highlightedEntry = navigableList[highlightedIndex];
|
|
352
|
+
const highlightedItemId =
|
|
353
|
+
open && highlightedEntry
|
|
354
|
+
? `${popoverId}-option-${highlightedEntry.id}`
|
|
355
|
+
: undefined;
|
|
356
|
+
|
|
310
357
|
return {
|
|
311
358
|
open,
|
|
312
359
|
query,
|
|
@@ -315,7 +362,8 @@ export const MentionResource = resource(
|
|
|
315
362
|
items: filteredItems,
|
|
316
363
|
highlightedIndex,
|
|
317
364
|
isSearchMode,
|
|
318
|
-
|
|
365
|
+
popoverId,
|
|
366
|
+
highlightedItemId,
|
|
319
367
|
selectCategory,
|
|
320
368
|
goBack,
|
|
321
369
|
selectItem,
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import { detectTrigger } from "./detectTrigger";
|
|
3
3
|
|
|
4
|
-
describe("
|
|
4
|
+
describe("detectTrigger", () => {
|
|
5
5
|
it("detects @query at cursor position", () => {
|
|
6
|
-
expect(
|
|
6
|
+
expect(detectTrigger("hello @wea", "@", 10)).toEqual({
|
|
7
7
|
query: "wea",
|
|
8
8
|
offset: 6,
|
|
9
9
|
});
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
it("returns null when cursor is before the trigger", () => {
|
|
13
|
-
expect(
|
|
13
|
+
expect(detectTrigger("hello @weather", "@", 5)).toBeNull();
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
it("returns null when no trigger character", () => {
|
|
17
|
-
expect(
|
|
17
|
+
expect(detectTrigger("hello world", "@", 11)).toBeNull();
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
it("requires whitespace or start before trigger", () => {
|
|
21
|
-
expect(
|
|
21
|
+
expect(detectTrigger("email@test", "@", 10)).toBeNull();
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
it("trigger at start of text", () => {
|
|
25
|
-
expect(
|
|
25
|
+
expect(detectTrigger("@foo", "@", 4)).toEqual({
|
|
26
26
|
query: "foo",
|
|
27
27
|
offset: 0,
|
|
28
28
|
});
|
|
@@ -30,19 +30,19 @@ describe("detectMentionTrigger", () => {
|
|
|
30
30
|
|
|
31
31
|
it("stops at whitespace in query", () => {
|
|
32
32
|
// "@foo bar" — space terminates the mention
|
|
33
|
-
expect(
|
|
33
|
+
expect(detectTrigger("@foo bar", "@", 8)).toBeNull();
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
it("stops at newline", () => {
|
|
37
|
-
expect(
|
|
37
|
+
expect(detectTrigger("@foo\nbar", "@", 8)).toBeNull();
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
it("stops at tab", () => {
|
|
41
|
-
expect(
|
|
41
|
+
expect(detectTrigger("@foo\tbar", "@", 8)).toBeNull();
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
it("treats tab before trigger as valid boundary", () => {
|
|
45
|
-
expect(
|
|
45
|
+
expect(detectTrigger("hello\t@foo", "@", 10)).toEqual({
|
|
46
46
|
query: "foo",
|
|
47
47
|
offset: 6,
|
|
48
48
|
});
|
|
@@ -51,7 +51,7 @@ describe("detectMentionTrigger", () => {
|
|
|
51
51
|
it("finds trigger closest to cursor, not earlier ones", () => {
|
|
52
52
|
// Text has two @: "hello @old text @new"
|
|
53
53
|
// Cursor at end → should find @new
|
|
54
|
-
expect(
|
|
54
|
+
expect(detectTrigger("hello @old text @new", "@", 20)).toEqual({
|
|
55
55
|
query: "new",
|
|
56
56
|
offset: 16,
|
|
57
57
|
});
|
|
@@ -59,18 +59,18 @@ describe("detectMentionTrigger", () => {
|
|
|
59
59
|
|
|
60
60
|
it("ignores trigger after cursor", () => {
|
|
61
61
|
// Cursor at position 5, trigger at position 10
|
|
62
|
-
expect(
|
|
62
|
+
expect(detectTrigger("hello text @foo", "@", 5)).toBeNull();
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
it("works with multi-char trigger", () => {
|
|
66
|
-
expect(
|
|
66
|
+
expect(detectTrigger("hello @@foo", "@@", 11)).toEqual({
|
|
67
67
|
query: "foo",
|
|
68
68
|
offset: 6,
|
|
69
69
|
});
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
it("empty query when cursor is right after trigger", () => {
|
|
73
|
-
expect(
|
|
73
|
+
expect(detectTrigger("hello @", "@", 7)).toEqual({
|
|
74
74
|
query: "",
|
|
75
75
|
offset: 6,
|
|
76
76
|
});
|
package/src/primitives/composer/{mention/detectMentionTrigger.ts → trigger/detectTrigger.ts}
RENAMED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const WHITESPACE_RE = /\s/;
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Detect a
|
|
4
|
+
* Detect a trigger character in text relative to the cursor position.
|
|
5
5
|
*
|
|
6
|
-
* @internal Exported for testing and for
|
|
6
|
+
* @internal Exported for testing and for trigger resources.
|
|
7
7
|
*/
|
|
8
|
-
export function
|
|
8
|
+
export function detectTrigger(
|
|
9
9
|
text: string,
|
|
10
10
|
triggerChar: string,
|
|
11
11
|
cursorPosition: number,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ComposerPrimitiveTriggerPopoverRoot,
|
|
3
|
+
useTriggerPopoverContext,
|
|
4
|
+
useTriggerPopoverContextOptional,
|
|
5
|
+
} from "./TriggerPopoverContext";
|
|
6
|
+
export { ComposerPrimitiveTriggerPopoverPopover } from "./TriggerPopoverPopover";
|
|
7
|
+
export {
|
|
8
|
+
ComposerPrimitiveTriggerPopoverCategories,
|
|
9
|
+
ComposerPrimitiveTriggerPopoverCategoryItem,
|
|
10
|
+
} from "./TriggerPopoverCategories";
|
|
11
|
+
export {
|
|
12
|
+
ComposerPrimitiveTriggerPopoverItems,
|
|
13
|
+
ComposerPrimitiveTriggerPopoverItem,
|
|
14
|
+
} from "./TriggerPopoverItems";
|
|
15
|
+
export { ComposerPrimitiveTriggerPopoverBack } from "./TriggerPopoverBack";
|
|
16
|
+
export type { OnSelectBehavior } from "./TriggerPopoverResource";
|
|
@@ -23,3 +23,19 @@ export { ComposerPrimitiveMentionItem as Unstable_MentionItem } from "./composer
|
|
|
23
23
|
export { ComposerPrimitiveMentionBack as Unstable_MentionBack } from "./composer/mention";
|
|
24
24
|
export { useMentionContext as unstable_useMentionContext } from "./composer/mention";
|
|
25
25
|
export { useMentionContextOptional as unstable_useMentionContextOptional } from "./composer/mention";
|
|
26
|
+
|
|
27
|
+
// --- Generic Trigger Popover primitives (unstable) ---
|
|
28
|
+
export { ComposerPrimitiveTriggerPopoverRoot as Unstable_TriggerPopoverRoot } from "./composer/trigger";
|
|
29
|
+
export { ComposerPrimitiveTriggerPopoverPopover as Unstable_TriggerPopoverPopover } from "./composer/trigger";
|
|
30
|
+
export { ComposerPrimitiveTriggerPopoverCategories as Unstable_TriggerPopoverCategories } from "./composer/trigger";
|
|
31
|
+
export { ComposerPrimitiveTriggerPopoverCategoryItem as Unstable_TriggerPopoverCategoryItem } from "./composer/trigger";
|
|
32
|
+
export { ComposerPrimitiveTriggerPopoverItems as Unstable_TriggerPopoverItems } from "./composer/trigger";
|
|
33
|
+
export { ComposerPrimitiveTriggerPopoverItem as Unstable_TriggerPopoverItem } from "./composer/trigger";
|
|
34
|
+
export { ComposerPrimitiveTriggerPopoverBack as Unstable_TriggerPopoverBack } from "./composer/trigger";
|
|
35
|
+
export { useTriggerPopoverContext as unstable_useTriggerPopoverContext } from "./composer/trigger";
|
|
36
|
+
export { useTriggerPopoverContextOptional as unstable_useTriggerPopoverContextOptional } from "./composer/trigger";
|
|
37
|
+
|
|
38
|
+
// --- Slash Command primitives (unstable) ---
|
|
39
|
+
// SlashCommandRoot is the only slash-specific primitive; UI primitives
|
|
40
|
+
// (Popover, Items, Categories, Back) are the shared TriggerPopover* set above.
|
|
41
|
+
export { ComposerPrimitiveSlashCommandRoot as Unstable_SlashCommandRoot } from "./composer/slash-command";
|
|
@@ -79,9 +79,20 @@ export namespace MessagePrimitiveRoot {
|
|
|
79
79
|
export type Element = ComponentRef<typeof Primitive.div>;
|
|
80
80
|
/**
|
|
81
81
|
* Props for the MessagePrimitive.Root component.
|
|
82
|
-
* Accepts all standard div element props.
|
|
82
|
+
* Accepts all standard div element props plus optional viewport slack tuning.
|
|
83
83
|
*/
|
|
84
|
-
export type Props = ComponentPropsWithoutRef<typeof Primitive.div
|
|
84
|
+
export type Props = ComponentPropsWithoutRef<typeof Primitive.div> & {
|
|
85
|
+
/**
|
|
86
|
+
* Threshold at which the user message height clamps to the offset.
|
|
87
|
+
* @default "10em"
|
|
88
|
+
*/
|
|
89
|
+
fillClampThreshold?: string | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* Offset used when clamping large user messages.
|
|
92
|
+
* @default "6em"
|
|
93
|
+
*/
|
|
94
|
+
fillClampOffset?: string | undefined;
|
|
95
|
+
};
|
|
85
96
|
}
|
|
86
97
|
|
|
87
98
|
/**
|
|
@@ -108,7 +119,7 @@ export namespace MessagePrimitiveRoot {
|
|
|
108
119
|
export const MessagePrimitiveRoot = forwardRef<
|
|
109
120
|
MessagePrimitiveRoot.Element,
|
|
110
121
|
MessagePrimitiveRoot.Props
|
|
111
|
-
>((props, forwardRef) => {
|
|
122
|
+
>(({ fillClampThreshold, fillClampOffset, ...props }, forwardRef) => {
|
|
112
123
|
const isHoveringRef = useIsHoveringRef();
|
|
113
124
|
const anchorUserMessageRef = useMessageViewportRef();
|
|
114
125
|
const ref = useComposedRefs<HTMLDivElement>(
|
|
@@ -119,7 +130,10 @@ export const MessagePrimitiveRoot = forwardRef<
|
|
|
119
130
|
const messageId = useAuiState((s) => s.message.id);
|
|
120
131
|
|
|
121
132
|
return (
|
|
122
|
-
<ThreadPrimitiveViewportSlack
|
|
133
|
+
<ThreadPrimitiveViewportSlack
|
|
134
|
+
fillClampThreshold={fillClampThreshold}
|
|
135
|
+
fillClampOffset={fillClampOffset}
|
|
136
|
+
>
|
|
123
137
|
<Primitive.div {...props} ref={ref} data-message-id={messageId} />
|
|
124
138
|
</ThreadPrimitiveViewportSlack>
|
|
125
139
|
);
|
|
@@ -36,9 +36,9 @@ const parseCssLength = (value: string, element: HTMLElement): number => {
|
|
|
36
36
|
|
|
37
37
|
export type ThreadViewportSlackProps = {
|
|
38
38
|
/** Threshold at which the user message height clamps to the offset */
|
|
39
|
-
fillClampThreshold?: string;
|
|
39
|
+
fillClampThreshold?: string | undefined;
|
|
40
40
|
/** Offset used when clamping large user messages */
|
|
41
|
-
fillClampOffset?: string;
|
|
41
|
+
fillClampOffset?: string | undefined;
|
|
42
42
|
children: ReactNode;
|
|
43
43
|
};
|
|
44
44
|
|
|
@@ -6,12 +6,14 @@ import type {
|
|
|
6
6
|
AppendMessage,
|
|
7
7
|
CreateAttachment,
|
|
8
8
|
PendingAttachment,
|
|
9
|
+
SendOptions,
|
|
9
10
|
} from "@assistant-ui/core";
|
|
10
11
|
|
|
11
12
|
class TestComposerCore extends BaseComposerRuntimeCore {
|
|
12
13
|
private _attachmentAdapter: AttachmentAdapter | undefined;
|
|
13
14
|
private _dictationAdapter: DictationAdapter | undefined;
|
|
14
15
|
public sentMessages: Array<Omit<AppendMessage, "parentId" | "sourceId">> = [];
|
|
16
|
+
public sentOptions: Array<SendOptions | undefined> = [];
|
|
15
17
|
public cancelCalled = false;
|
|
16
18
|
|
|
17
19
|
protected getAttachmentAdapter() {
|
|
@@ -33,8 +35,12 @@ class TestComposerCore extends BaseComposerRuntimeCore {
|
|
|
33
35
|
return false;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
protected handleSend(
|
|
38
|
+
protected handleSend(
|
|
39
|
+
message: Omit<AppendMessage, "parentId" | "sourceId">,
|
|
40
|
+
options?: SendOptions,
|
|
41
|
+
) {
|
|
37
42
|
this.sentMessages.push(message);
|
|
43
|
+
this.sentOptions.push(options);
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
protected handleCancel() {
|
|
@@ -421,4 +427,30 @@ describe("BaseComposerRuntimeCore", () => {
|
|
|
421
427
|
expect(adapter.send).toHaveBeenCalledTimes(1);
|
|
422
428
|
});
|
|
423
429
|
});
|
|
430
|
+
|
|
431
|
+
describe("send options", () => {
|
|
432
|
+
it("send() passes undefined options by default", async () => {
|
|
433
|
+
composer.setText("hello");
|
|
434
|
+
await composer.send();
|
|
435
|
+
|
|
436
|
+
expect(composer.sentOptions).toHaveLength(1);
|
|
437
|
+
expect(composer.sentOptions[0]).toBeUndefined();
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it("send({ startRun: true }) forwards options to handleSend", async () => {
|
|
441
|
+
composer.setText("hello");
|
|
442
|
+
await composer.send({ startRun: true });
|
|
443
|
+
|
|
444
|
+
expect(composer.sentOptions).toHaveLength(1);
|
|
445
|
+
expect(composer.sentOptions[0]).toEqual({ startRun: true });
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it("send({ startRun: false }) forwards options to handleSend", async () => {
|
|
449
|
+
composer.setText("hello");
|
|
450
|
+
await composer.send({ startRun: false });
|
|
451
|
+
|
|
452
|
+
expect(composer.sentOptions).toHaveLength(1);
|
|
453
|
+
expect(composer.sentOptions[0]).toEqual({ startRun: false });
|
|
454
|
+
});
|
|
455
|
+
});
|
|
424
456
|
});
|