@agent-native/toolkit 0.21.3 → 0.22.0-nightly-20260926081002
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/agent-native.eject.json +11 -0
- package/dist/agentkit.d.ts +5 -1
- package/dist/agentkit.d.ts.map +1 -1
- package/dist/agentkit.js +2 -0
- package/dist/agentkit.js.map +1 -1
- package/dist/app-shell/PromptHome.d.ts +26 -0
- package/dist/app-shell/PromptHome.d.ts.map +1 -0
- package/dist/app-shell/PromptHome.js +19 -0
- package/dist/app-shell/PromptHome.js.map +1 -0
- package/dist/app-shell/PromptHome.spec.d.ts +2 -0
- package/dist/app-shell/PromptHome.spec.d.ts.map +1 -0
- package/dist/app-shell/PromptHome.spec.js +206 -0
- package/dist/app-shell/PromptHome.spec.js.map +1 -0
- package/dist/app-shell/TemplateLibraryGrid.d.ts +48 -0
- package/dist/app-shell/TemplateLibraryGrid.d.ts.map +1 -0
- package/dist/app-shell/TemplateLibraryGrid.js +20 -0
- package/dist/app-shell/TemplateLibraryGrid.js.map +1 -0
- package/dist/app-shell/TemplatePreviewDialog.d.ts +41 -0
- package/dist/app-shell/TemplatePreviewDialog.d.ts.map +1 -0
- package/dist/app-shell/TemplatePreviewDialog.js +50 -0
- package/dist/app-shell/TemplatePreviewDialog.js.map +1 -0
- package/dist/app-shell/TemplatePreviewDialog.spec.d.ts +2 -0
- package/dist/app-shell/TemplatePreviewDialog.spec.d.ts.map +1 -0
- package/dist/app-shell/TemplatePreviewDialog.spec.js +258 -0
- package/dist/app-shell/TemplatePreviewDialog.spec.js.map +1 -0
- package/dist/app-shell/index.d.ts +3 -0
- package/dist/app-shell/index.d.ts.map +1 -1
- package/dist/app-shell/index.js +3 -0
- package/dist/app-shell/index.js.map +1 -1
- package/dist/app-shell/prompt-home.css +159 -0
- package/dist/composer/ComposerContextMenu.d.ts +48 -0
- package/dist/composer/ComposerContextMenu.d.ts.map +1 -0
- package/dist/composer/ComposerContextMenu.js +295 -0
- package/dist/composer/ComposerContextMenu.js.map +1 -0
- package/dist/composer/ComposerContextMenu.spec.d.ts +2 -0
- package/dist/composer/ComposerContextMenu.spec.d.ts.map +1 -0
- package/dist/composer/ComposerContextMenu.spec.js +270 -0
- package/dist/composer/ComposerContextMenu.spec.js.map +1 -0
- package/dist/composer/ComposerContextMenuSearch.d.ts +11 -0
- package/dist/composer/ComposerContextMenuSearch.d.ts.map +1 -0
- package/dist/composer/ComposerContextMenuSearch.js +42 -0
- package/dist/composer/ComposerContextMenuSearch.js.map +1 -0
- package/dist/composer/ComposerContextPicker.d.ts +7 -0
- package/dist/composer/ComposerContextPicker.d.ts.map +1 -0
- package/dist/composer/ComposerContextPicker.js +71 -0
- package/dist/composer/ComposerContextPicker.js.map +1 -0
- package/dist/composer/ComposerContextPicker.spec.d.ts +2 -0
- package/dist/composer/ComposerContextPicker.spec.d.ts.map +1 -0
- package/dist/composer/ComposerContextPicker.spec.js +454 -0
- package/dist/composer/ComposerContextPicker.spec.js.map +1 -0
- package/dist/composer/ComposerContextPickerDialog.d.ts +8 -0
- package/dist/composer/ComposerContextPickerDialog.d.ts.map +1 -0
- package/dist/composer/ComposerContextPickerDialog.js +101 -0
- package/dist/composer/ComposerContextPickerDialog.js.map +1 -0
- package/dist/composer/ComposerContextPickerDialog.spec.d.ts +2 -0
- package/dist/composer/ComposerContextPickerDialog.spec.d.ts.map +1 -0
- package/dist/composer/ComposerContextPickerDialog.spec.js +329 -0
- package/dist/composer/ComposerContextPickerDialog.spec.js.map +1 -0
- package/dist/composer/ComposerContextSearchInput.d.ts +7 -0
- package/dist/composer/ComposerContextSearchInput.d.ts.map +1 -0
- package/dist/composer/ComposerContextSearchInput.js +18 -0
- package/dist/composer/ComposerContextSearchInput.js.map +1 -0
- package/dist/composer/PromptComposer.context.spec.d.ts +2 -0
- package/dist/composer/PromptComposer.context.spec.d.ts.map +1 -0
- package/dist/composer/PromptComposer.context.spec.js +383 -0
- package/dist/composer/PromptComposer.context.spec.js.map +1 -0
- package/dist/composer/PromptComposer.d.ts +19 -5
- package/dist/composer/PromptComposer.d.ts.map +1 -1
- package/dist/composer/PromptComposer.js +30 -25
- package/dist/composer/PromptComposer.js.map +1 -1
- package/dist/composer/PromptComposer.spec.js +24 -1
- package/dist/composer/PromptComposer.spec.js.map +1 -1
- package/dist/composer/TiptapComposer.d.ts +13 -2
- package/dist/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/composer/TiptapComposer.js +89 -32
- package/dist/composer/TiptapComposer.js.map +1 -1
- package/dist/composer/context-items.d.ts +12 -0
- package/dist/composer/context-items.d.ts.map +1 -0
- package/dist/composer/context-items.js +46 -0
- package/dist/composer/context-items.js.map +1 -0
- package/dist/composer/context-items.spec.d.ts +2 -0
- package/dist/composer/context-items.spec.d.ts.map +1 -0
- package/dist/composer/context-items.spec.js +43 -0
- package/dist/composer/context-items.spec.js.map +1 -0
- package/dist/composer/index.d.ts +4 -1
- package/dist/composer/index.d.ts.map +1 -1
- package/dist/composer/index.js +3 -1
- package/dist/composer/index.js.map +1 -1
- package/dist/composer/runtime-adapters.d.ts +6 -1
- package/dist/composer/runtime-adapters.d.ts.map +1 -1
- package/dist/composer/runtime-adapters.js.map +1 -1
- package/dist/composer/useComposerContextPicker.d.ts +111 -0
- package/dist/composer/useComposerContextPicker.d.ts.map +1 -0
- package/dist/composer/useComposerContextPicker.js +299 -0
- package/dist/composer/useComposerContextPicker.js.map +1 -0
- package/dist/design-system/default-adapter.d.ts.map +1 -1
- package/dist/design-system/default-adapter.js +13 -7
- package/dist/design-system/default-adapter.js.map +1 -1
- package/dist/design-system/default-adapter.spec.js +13 -0
- package/dist/design-system/default-adapter.spec.js.map +1 -1
- package/dist/design-system/types.d.ts +4 -1
- package/dist/design-system/types.d.ts.map +1 -1
- package/dist/styles.css +1 -0
- package/dist/ui/command.d.ts +8 -2
- package/dist/ui/command.d.ts.map +1 -1
- package/dist/ui/command.js +9 -2
- package/dist/ui/command.js.map +1 -1
- package/dist/ui/command.spec.js +20 -1
- package/dist/ui/command.spec.js.map +1 -1
- package/package.json +2 -1
- package/src/agentkit.ts +26 -0
- package/src/app-shell/PromptHome.spec.tsx +404 -0
- package/src/app-shell/PromptHome.tsx +111 -0
- package/src/app-shell/TemplateLibraryGrid.tsx +212 -0
- package/src/app-shell/TemplatePreviewDialog.spec.tsx +473 -0
- package/src/app-shell/TemplatePreviewDialog.tsx +224 -0
- package/src/app-shell/index.ts +3 -0
- package/src/app-shell/prompt-home.css +159 -0
- package/src/composer/ComposerContextMenu.spec.tsx +343 -0
- package/src/composer/ComposerContextMenu.tsx +610 -0
- package/src/composer/ComposerContextMenuSearch.tsx +69 -0
- package/src/composer/ComposerContextPicker.spec.tsx +520 -0
- package/src/composer/ComposerContextPicker.tsx +280 -0
- package/src/composer/ComposerContextPickerDialog.spec.tsx +405 -0
- package/src/composer/ComposerContextPickerDialog.tsx +274 -0
- package/src/composer/ComposerContextSearchInput.tsx +48 -0
- package/src/composer/PromptComposer.context.spec.tsx +577 -0
- package/src/composer/PromptComposer.spec.ts +24 -1
- package/src/composer/PromptComposer.tsx +60 -26
- package/src/composer/TiptapComposer.tsx +198 -48
- package/src/composer/context-items.spec.ts +68 -0
- package/src/composer/context-items.ts +72 -0
- package/src/composer/index.ts +27 -0
- package/src/composer/runtime-adapters.tsx +6 -1
- package/src/composer/useComposerContextPicker.ts +441 -0
- package/src/design-system/default-adapter.spec.tsx +23 -0
- package/src/design-system/default-adapter.tsx +52 -23
- package/src/design-system/types.ts +4 -1
- package/src/styles.css +1 -0
- package/src/ui/command.spec.tsx +33 -1
- package/src/ui/command.tsx +52 -9
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
useRef,
|
|
4
|
+
useState,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
type ReactElement,
|
|
7
|
+
} from "react";
|
|
8
|
+
|
|
9
|
+
import { formatAttachmentError } from "./attachment-accept.js";
|
|
10
|
+
import { useComposerRuntimeAdapters } from "./runtime-adapters.js";
|
|
11
|
+
|
|
12
|
+
export interface ComposerContextPickerItem {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
url?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ComposerContextPickerRequest {
|
|
19
|
+
search: string;
|
|
20
|
+
page: number;
|
|
21
|
+
cursor?: string;
|
|
22
|
+
url?: string;
|
|
23
|
+
signal: AbortSignal;
|
|
24
|
+
}
|
|
25
|
+
export interface ComposerContextPickerResult {
|
|
26
|
+
items: readonly ComposerContextPickerItem[];
|
|
27
|
+
hasMore?: boolean;
|
|
28
|
+
nextCursor?: string;
|
|
29
|
+
}
|
|
30
|
+
export type ComposerContextPickerSelection =
|
|
31
|
+
| void
|
|
32
|
+
| boolean
|
|
33
|
+
| Promise<void | boolean>;
|
|
34
|
+
export type ComposerContextPickerFooterAction = {
|
|
35
|
+
label: string;
|
|
36
|
+
icon?: ReactNode;
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
} & (
|
|
39
|
+
| { onSelect: () => ComposerContextPickerSelection; renderLink?: never }
|
|
40
|
+
| { renderLink: (children: ReactNode) => ReactElement; onSelect?: never }
|
|
41
|
+
);
|
|
42
|
+
export type ComposerContextPickerConfig = {
|
|
43
|
+
footerAction?: ComposerContextPickerFooterAction;
|
|
44
|
+
searchPlaceholder: string;
|
|
45
|
+
selectedIds?: readonly string[];
|
|
46
|
+
scopeKey?: string;
|
|
47
|
+
refreshKey?: string | number;
|
|
48
|
+
link?: {
|
|
49
|
+
label?: string;
|
|
50
|
+
placeholder: string;
|
|
51
|
+
submitLabel: string;
|
|
52
|
+
validate?: (url: string) => string | undefined;
|
|
53
|
+
};
|
|
54
|
+
clearSelection?: {
|
|
55
|
+
label: string;
|
|
56
|
+
onSelect: () => ComposerContextPickerSelection;
|
|
57
|
+
};
|
|
58
|
+
emptyMessage?: string;
|
|
59
|
+
} & (
|
|
60
|
+
| {
|
|
61
|
+
items?: readonly ComposerContextPickerItem[];
|
|
62
|
+
loading?: boolean;
|
|
63
|
+
error?: string;
|
|
64
|
+
onRetry?: () => unknown;
|
|
65
|
+
load?: never;
|
|
66
|
+
}
|
|
67
|
+
| {
|
|
68
|
+
load: (
|
|
69
|
+
request: ComposerContextPickerRequest,
|
|
70
|
+
) => Promise<ComposerContextPickerResult>;
|
|
71
|
+
items?: never;
|
|
72
|
+
loading?: never;
|
|
73
|
+
error?: never;
|
|
74
|
+
onRetry?: never;
|
|
75
|
+
}
|
|
76
|
+
) &
|
|
77
|
+
(
|
|
78
|
+
| {
|
|
79
|
+
presentation?: "submenu" | { type: "dialog"; mode: "url" };
|
|
80
|
+
onSelect: (
|
|
81
|
+
item: ComposerContextPickerItem,
|
|
82
|
+
request: ComposerContextPickerRequest,
|
|
83
|
+
) => ComposerContextPickerSelection;
|
|
84
|
+
}
|
|
85
|
+
| {
|
|
86
|
+
presentation: {
|
|
87
|
+
type: "dialog";
|
|
88
|
+
mode: "multiple";
|
|
89
|
+
onAttach: (
|
|
90
|
+
items: readonly ComposerContextPickerItem[],
|
|
91
|
+
request: ComposerContextPickerRequest,
|
|
92
|
+
) => ComposerContextPickerSelection;
|
|
93
|
+
};
|
|
94
|
+
onSelect?: never;
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
type PickerLocation = Omit<ComposerContextPickerRequest, "signal">;
|
|
99
|
+
type LoadState = {
|
|
100
|
+
key: string;
|
|
101
|
+
status: "loading" | "ready" | "error";
|
|
102
|
+
result?: ComposerContextPickerResult;
|
|
103
|
+
error?: string;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
function validResult(result: ComposerContextPickerResult): boolean {
|
|
107
|
+
return Boolean(
|
|
108
|
+
result &&
|
|
109
|
+
Array.isArray(result.items) &&
|
|
110
|
+
result.items.every(
|
|
111
|
+
(item) =>
|
|
112
|
+
item &&
|
|
113
|
+
typeof item.id === "string" &&
|
|
114
|
+
typeof item.title === "string" &&
|
|
115
|
+
(item.disabled === undefined || typeof item.disabled === "boolean") &&
|
|
116
|
+
(item.url === undefined || typeof item.url === "string"),
|
|
117
|
+
) &&
|
|
118
|
+
(result.hasMore === undefined || typeof result.hasMore === "boolean") &&
|
|
119
|
+
(result.nextCursor === undefined || typeof result.nextCursor === "string"),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function useComposerContextPicker({
|
|
124
|
+
config,
|
|
125
|
+
onClose,
|
|
126
|
+
}: {
|
|
127
|
+
config: ComposerContextPickerConfig;
|
|
128
|
+
onClose: () => void;
|
|
129
|
+
}) {
|
|
130
|
+
const t = useComposerRuntimeAdapters().translate!;
|
|
131
|
+
const configRef = useRef(config);
|
|
132
|
+
configRef.current = config;
|
|
133
|
+
const translateRef = useRef(t);
|
|
134
|
+
translateRef.current = t;
|
|
135
|
+
const presentation =
|
|
136
|
+
typeof config.presentation === "object" ? config.presentation : undefined;
|
|
137
|
+
const [chosen, setChosen] = useState<
|
|
138
|
+
ReadonlyMap<string, ComposerContextPickerItem>
|
|
139
|
+
>(() => new Map());
|
|
140
|
+
const [stage, setStage] = useState<"link" | "results">(
|
|
141
|
+
config.link || presentation ? "link" : "results",
|
|
142
|
+
);
|
|
143
|
+
const [link, setLink] = useState("");
|
|
144
|
+
const [location, setLocation] = useState<PickerLocation>({
|
|
145
|
+
search: "",
|
|
146
|
+
page: 1,
|
|
147
|
+
});
|
|
148
|
+
const cursors = useRef<Array<string | undefined>>([undefined]);
|
|
149
|
+
const [revision, setRevision] = useState(0);
|
|
150
|
+
const [loaded, setLoaded] = useState<LoadState>({
|
|
151
|
+
key: "",
|
|
152
|
+
status: "loading",
|
|
153
|
+
});
|
|
154
|
+
const [actionError, setActionError] = useState<{
|
|
155
|
+
message: string;
|
|
156
|
+
retry?: () => void;
|
|
157
|
+
}>();
|
|
158
|
+
const [selecting, setSelecting] = useState(false);
|
|
159
|
+
const epoch = useRef(0);
|
|
160
|
+
const pendingSelection = useRef<AbortController | null>(null);
|
|
161
|
+
const remote = Boolean(config.load);
|
|
162
|
+
const key = JSON.stringify([location, config.refreshKey]);
|
|
163
|
+
const keyRef = useRef(key);
|
|
164
|
+
keyRef.current = key;
|
|
165
|
+
|
|
166
|
+
const invalidateSelection = () => {
|
|
167
|
+
epoch.current++;
|
|
168
|
+
pendingSelection.current?.abort();
|
|
169
|
+
pendingSelection.current = null;
|
|
170
|
+
setSelecting(false);
|
|
171
|
+
setActionError(undefined);
|
|
172
|
+
};
|
|
173
|
+
useEffect(
|
|
174
|
+
() => () => {
|
|
175
|
+
epoch.current++;
|
|
176
|
+
pendingSelection.current?.abort();
|
|
177
|
+
},
|
|
178
|
+
[],
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (!remote || stage !== "results") return;
|
|
183
|
+
const abort = new AbortController();
|
|
184
|
+
const loader = configRef.current.load!;
|
|
185
|
+
const request = { ...location, signal: abort.signal };
|
|
186
|
+
setLoaded((previous) => ({
|
|
187
|
+
key,
|
|
188
|
+
status: "loading",
|
|
189
|
+
result: previous.result,
|
|
190
|
+
}));
|
|
191
|
+
void Promise.resolve()
|
|
192
|
+
.then(() => {
|
|
193
|
+
if (abort.signal.aborted) return;
|
|
194
|
+
return loader(request);
|
|
195
|
+
})
|
|
196
|
+
.then((result) => {
|
|
197
|
+
if (abort.signal.aborted || keyRef.current !== key) return;
|
|
198
|
+
if (!result || !validResult(result))
|
|
199
|
+
throw new Error(
|
|
200
|
+
translateRef.current("agentChat.composer.contextLoadFailed", {
|
|
201
|
+
defaultValue: "Could not load context.",
|
|
202
|
+
}),
|
|
203
|
+
);
|
|
204
|
+
setLoaded({ key, status: "ready", result });
|
|
205
|
+
})
|
|
206
|
+
.catch((cause: unknown) => {
|
|
207
|
+
if (abort.signal.aborted || keyRef.current !== key) return;
|
|
208
|
+
setLoaded({
|
|
209
|
+
key,
|
|
210
|
+
status: "error",
|
|
211
|
+
error: formatAttachmentError(
|
|
212
|
+
cause,
|
|
213
|
+
translateRef.current("agentChat.composer.contextLoadFailed", {
|
|
214
|
+
defaultValue: "Could not load context.",
|
|
215
|
+
}),
|
|
216
|
+
),
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
return () => abort.abort();
|
|
220
|
+
}, [key, location, remote, stage, revision]);
|
|
221
|
+
|
|
222
|
+
const move = (next: PickerLocation) => {
|
|
223
|
+
invalidateSelection();
|
|
224
|
+
setLocation(next);
|
|
225
|
+
};
|
|
226
|
+
const back = () => {
|
|
227
|
+
invalidateSelection();
|
|
228
|
+
if (stage === "results" && (config.link || presentation)) {
|
|
229
|
+
setStage("link");
|
|
230
|
+
setChosen(new Map());
|
|
231
|
+
setLocation({ search: "", page: 1 });
|
|
232
|
+
cursors.current = [undefined];
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
const select = async (
|
|
236
|
+
item?: ComposerContextPickerItem | "footer" | "batch" | "url",
|
|
237
|
+
) => {
|
|
238
|
+
if (pendingSelection.current) return;
|
|
239
|
+
const abort = new AbortController();
|
|
240
|
+
const generation = ++epoch.current;
|
|
241
|
+
pendingSelection.current = abort;
|
|
242
|
+
setSelecting(true);
|
|
243
|
+
setActionError(undefined);
|
|
244
|
+
try {
|
|
245
|
+
const active = configRef.current;
|
|
246
|
+
const request = Object.freeze({
|
|
247
|
+
...location,
|
|
248
|
+
...(item === "url" ? { url: link.trim() } : {}),
|
|
249
|
+
signal: abort.signal,
|
|
250
|
+
});
|
|
251
|
+
const dialog =
|
|
252
|
+
typeof active.presentation === "object"
|
|
253
|
+
? active.presentation
|
|
254
|
+
: undefined;
|
|
255
|
+
const batch = Object.freeze(
|
|
256
|
+
[...chosen.values()]
|
|
257
|
+
.filter((value) => !active.selectedIds?.includes(value.id))
|
|
258
|
+
.map((value) => Object.freeze({ ...value })),
|
|
259
|
+
);
|
|
260
|
+
const result =
|
|
261
|
+
item === "batch" && dialog?.mode === "multiple"
|
|
262
|
+
? await dialog.onAttach(batch, request)
|
|
263
|
+
: item === "url"
|
|
264
|
+
? await active.onSelect!(
|
|
265
|
+
{ id: link.trim(), title: link.trim(), url: link.trim() },
|
|
266
|
+
request,
|
|
267
|
+
)
|
|
268
|
+
: item === "footer"
|
|
269
|
+
? await configRef.current.footerAction?.onSelect?.()
|
|
270
|
+
: item && typeof item === "object"
|
|
271
|
+
? await active.onSelect!(item, request)
|
|
272
|
+
: await configRef.current.clearSelection?.onSelect();
|
|
273
|
+
if (abort.signal.aborted || epoch.current !== generation) return;
|
|
274
|
+
if (result !== false) onClose();
|
|
275
|
+
} catch (cause) {
|
|
276
|
+
if (abort.signal.aborted || epoch.current !== generation) return;
|
|
277
|
+
setActionError({
|
|
278
|
+
message: formatAttachmentError(
|
|
279
|
+
cause,
|
|
280
|
+
t("agentChat.composer.contextActionFailed", {
|
|
281
|
+
defaultValue: "Could not add context.",
|
|
282
|
+
}),
|
|
283
|
+
),
|
|
284
|
+
retry: () => {
|
|
285
|
+
void select(item);
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
} finally {
|
|
289
|
+
if (epoch.current === generation) {
|
|
290
|
+
pendingSelection.current = null;
|
|
291
|
+
setSelecting(false);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
const retryLoad = async () => {
|
|
296
|
+
setActionError(undefined);
|
|
297
|
+
if (remote) {
|
|
298
|
+
setRevision((value) => value + 1);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const generation = epoch.current;
|
|
302
|
+
try {
|
|
303
|
+
await configRef.current.onRetry?.();
|
|
304
|
+
} catch (cause) {
|
|
305
|
+
if (epoch.current !== generation) return;
|
|
306
|
+
setActionError({
|
|
307
|
+
message: formatAttachmentError(
|
|
308
|
+
cause,
|
|
309
|
+
t("agentChat.composer.contextLoadFailed", {
|
|
310
|
+
defaultValue: "Could not load context.",
|
|
311
|
+
}),
|
|
312
|
+
),
|
|
313
|
+
retry: () => {
|
|
314
|
+
void retryLoad();
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
const validateLink = (value: string): string | undefined => {
|
|
320
|
+
try {
|
|
321
|
+
if (!value)
|
|
322
|
+
return t("agentChat.composer.contextLinkRequired", {
|
|
323
|
+
defaultValue: "Enter a link.",
|
|
324
|
+
});
|
|
325
|
+
if (presentation) {
|
|
326
|
+
let parsed: URL;
|
|
327
|
+
try {
|
|
328
|
+
parsed = new URL(value);
|
|
329
|
+
} catch {
|
|
330
|
+
return t("agentChat.composer.contextInvalidUrl", {
|
|
331
|
+
defaultValue: "Enter a valid HTTP or HTTPS URL.",
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
if (
|
|
335
|
+
!["http:", "https:"].includes(parsed.protocol) ||
|
|
336
|
+
!parsed.hostname ||
|
|
337
|
+
parsed.username ||
|
|
338
|
+
parsed.password
|
|
339
|
+
) {
|
|
340
|
+
return t("agentChat.composer.contextInvalidUrl", {
|
|
341
|
+
defaultValue: "Enter a valid HTTP or HTTPS URL.",
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return config.link?.validate?.(value);
|
|
346
|
+
} catch (cause) {
|
|
347
|
+
return formatAttachmentError(
|
|
348
|
+
cause,
|
|
349
|
+
t("agentChat.composer.contextLoadFailed", {
|
|
350
|
+
defaultValue: "Could not load context.",
|
|
351
|
+
}),
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
const linkError = validateLink(link.trim());
|
|
356
|
+
const submitLink = () => {
|
|
357
|
+
const value = link.trim();
|
|
358
|
+
const message = validateLink(value);
|
|
359
|
+
if (presentation && message) return;
|
|
360
|
+
if (message) {
|
|
361
|
+
setActionError({ message });
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (presentation?.mode === "url") {
|
|
365
|
+
void select("url");
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
move({ search: "", page: 1, url: value });
|
|
369
|
+
cursors.current = [undefined];
|
|
370
|
+
setStage("results");
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const loading = remote
|
|
374
|
+
? loaded.key !== key || loaded.status === "loading"
|
|
375
|
+
: config.loading === true;
|
|
376
|
+
const error =
|
|
377
|
+
actionError?.message ??
|
|
378
|
+
(remote
|
|
379
|
+
? loaded.key === key && loaded.status === "error"
|
|
380
|
+
? loaded.error
|
|
381
|
+
: undefined
|
|
382
|
+
: config.error);
|
|
383
|
+
const result = remote ? loaded.result : undefined;
|
|
384
|
+
const terms = location.search
|
|
385
|
+
.trim()
|
|
386
|
+
.toLocaleLowerCase()
|
|
387
|
+
.split(/\s+/)
|
|
388
|
+
.filter(Boolean);
|
|
389
|
+
const items = remote
|
|
390
|
+
? (result?.items ?? [])
|
|
391
|
+
: (config.items ?? []).filter((item) =>
|
|
392
|
+
terms.every((term) => item.title.toLocaleLowerCase().includes(term)),
|
|
393
|
+
);
|
|
394
|
+
const hasMore = result?.hasMore ?? Boolean(result?.nextCursor);
|
|
395
|
+
const retry =
|
|
396
|
+
actionError?.retry ??
|
|
397
|
+
(remote || config.onRetry
|
|
398
|
+
? () => {
|
|
399
|
+
void retryLoad();
|
|
400
|
+
}
|
|
401
|
+
: undefined);
|
|
402
|
+
const selectedItems = [...chosen.values()].filter(
|
|
403
|
+
(item) => !config.selectedIds?.includes(item.id),
|
|
404
|
+
);
|
|
405
|
+
const toggleItem = (item: ComposerContextPickerItem, checked: boolean) => {
|
|
406
|
+
if (selecting || item.disabled || config.selectedIds?.includes(item.id))
|
|
407
|
+
return;
|
|
408
|
+
setActionError(undefined);
|
|
409
|
+
setChosen((previous) => {
|
|
410
|
+
const next = new Map(previous);
|
|
411
|
+
if (checked) next.set(item.id, item);
|
|
412
|
+
else next.delete(item.id);
|
|
413
|
+
return next;
|
|
414
|
+
});
|
|
415
|
+
};
|
|
416
|
+
return {
|
|
417
|
+
t,
|
|
418
|
+
config,
|
|
419
|
+
stage,
|
|
420
|
+
link,
|
|
421
|
+
setLink,
|
|
422
|
+
location,
|
|
423
|
+
cursors,
|
|
424
|
+
move,
|
|
425
|
+
back,
|
|
426
|
+
select,
|
|
427
|
+
submitLink,
|
|
428
|
+
loading,
|
|
429
|
+
error,
|
|
430
|
+
items,
|
|
431
|
+
hasMore,
|
|
432
|
+
retry,
|
|
433
|
+
selecting,
|
|
434
|
+
actionError,
|
|
435
|
+
setActionError,
|
|
436
|
+
result,
|
|
437
|
+
linkError,
|
|
438
|
+
selectedItems,
|
|
439
|
+
toggleItem,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
@@ -121,6 +121,29 @@ describe("default design system adapter", () => {
|
|
|
121
121
|
expect(preventDefault).toHaveBeenCalledOnce();
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
+
it("keeps outside dismissal enabled when the close control is hidden", () => {
|
|
125
|
+
const dialog = renderComponent(defaultDesignSystemComponents.Dialog, {
|
|
126
|
+
open: true,
|
|
127
|
+
onOpenChange: vi.fn(),
|
|
128
|
+
title: "Preview",
|
|
129
|
+
children: <p>Preview content</p>,
|
|
130
|
+
hideClose: true,
|
|
131
|
+
});
|
|
132
|
+
const content = findElement(
|
|
133
|
+
dialog,
|
|
134
|
+
(element) => typeof element.props.onInteractOutside === "function",
|
|
135
|
+
);
|
|
136
|
+
const preventDefault = vi.fn();
|
|
137
|
+
|
|
138
|
+
(
|
|
139
|
+
content?.props.onInteractOutside as
|
|
140
|
+
| ((event: { preventDefault: () => void }) => void)
|
|
141
|
+
| undefined
|
|
142
|
+
)?.({ preventDefault });
|
|
143
|
+
|
|
144
|
+
expect(preventDefault).not.toHaveBeenCalled();
|
|
145
|
+
});
|
|
146
|
+
|
|
124
147
|
it("honors menu selection state and closeOnAction", () => {
|
|
125
148
|
const onAction = vi.fn();
|
|
126
149
|
const menu = renderComponent(defaultDesignSystemComponents.Menu, {
|
|
@@ -612,9 +612,11 @@ const DefaultDialog: DesignSystemComponents["Dialog"] = ({
|
|
|
612
612
|
description,
|
|
613
613
|
children,
|
|
614
614
|
footer,
|
|
615
|
+
headerAction,
|
|
615
616
|
trigger,
|
|
616
617
|
size = "medium",
|
|
617
618
|
dismissible = true,
|
|
619
|
+
hideClose,
|
|
618
620
|
closeLabel,
|
|
619
621
|
initialFocusRef,
|
|
620
622
|
restoreFocusRef,
|
|
@@ -626,13 +628,16 @@ const DefaultDialog: DesignSystemComponents["Dialog"] = ({
|
|
|
626
628
|
{trigger ? <DialogTrigger asChild>{trigger}</DialogTrigger> : null}
|
|
627
629
|
<DialogContent
|
|
628
630
|
container={portalContainer}
|
|
629
|
-
hideClose={!dismissible}
|
|
631
|
+
hideClose={hideClose || !dismissible}
|
|
630
632
|
closeLabel={closeLabel}
|
|
633
|
+
{...(!description ? { "aria-describedby": undefined } : {})}
|
|
631
634
|
style={style}
|
|
632
635
|
className={cn(
|
|
633
636
|
size === "small" && "max-w-sm",
|
|
634
637
|
size === "medium" && "max-w-lg",
|
|
635
638
|
size === "large" && "max-w-2xl",
|
|
639
|
+
size === "viewport" &&
|
|
640
|
+
"h-[90dvh] max-h-[90dvh] w-[calc(100vw-2rem)] max-w-[96rem] sm:w-[calc(100vw-3rem)]",
|
|
636
641
|
size === "fullscreen" &&
|
|
637
642
|
"inset-0 h-[100dvh] max-h-none w-full max-w-none translate-x-0 translate-y-0 rounded-none",
|
|
638
643
|
className,
|
|
@@ -655,8 +660,17 @@ const DefaultDialog: DesignSystemComponents["Dialog"] = ({
|
|
|
655
660
|
if (!dismissible) event.preventDefault();
|
|
656
661
|
}}
|
|
657
662
|
>
|
|
658
|
-
<DialogHeader
|
|
659
|
-
|
|
663
|
+
<DialogHeader
|
|
664
|
+
className={
|
|
665
|
+
headerAction
|
|
666
|
+
? "flex-row items-center justify-between gap-3 space-y-0"
|
|
667
|
+
: undefined
|
|
668
|
+
}
|
|
669
|
+
>
|
|
670
|
+
<DialogTitle className={headerAction ? "min-w-0" : undefined}>
|
|
671
|
+
{title}
|
|
672
|
+
</DialogTitle>
|
|
673
|
+
{headerAction}
|
|
660
674
|
{description ? (
|
|
661
675
|
<DialogDescription>{description}</DialogDescription>
|
|
662
676
|
) : null}
|
|
@@ -874,22 +888,12 @@ const DefaultTabs: DesignSystemComponents["Tabs"] = ({
|
|
|
874
888
|
onChange,
|
|
875
889
|
orientation,
|
|
876
890
|
activationMode,
|
|
891
|
+
headerActions,
|
|
877
892
|
className,
|
|
878
893
|
style,
|
|
879
894
|
...props
|
|
880
|
-
}) =>
|
|
881
|
-
|
|
882
|
-
{...props}
|
|
883
|
-
value={String(value)}
|
|
884
|
-
onValueChange={(next) => {
|
|
885
|
-
const item = items.find((candidate) => String(candidate.value) === next);
|
|
886
|
-
if (item) onChange(item.value);
|
|
887
|
-
}}
|
|
888
|
-
orientation={orientation}
|
|
889
|
-
activationMode={activationMode}
|
|
890
|
-
className={className}
|
|
891
|
-
style={style}
|
|
892
|
-
>
|
|
895
|
+
}) => {
|
|
896
|
+
const tabList = (
|
|
893
897
|
<TabsList>
|
|
894
898
|
{items.map((item) => (
|
|
895
899
|
<TabsTrigger
|
|
@@ -902,13 +906,38 @@ const DefaultTabs: DesignSystemComponents["Tabs"] = ({
|
|
|
902
906
|
</TabsTrigger>
|
|
903
907
|
))}
|
|
904
908
|
</TabsList>
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
909
|
+
);
|
|
910
|
+
return (
|
|
911
|
+
<DefaultTabsPrimitive
|
|
912
|
+
{...props}
|
|
913
|
+
value={String(value)}
|
|
914
|
+
onValueChange={(next) => {
|
|
915
|
+
const item = items.find(
|
|
916
|
+
(candidate) => String(candidate.value) === next,
|
|
917
|
+
);
|
|
918
|
+
if (item) onChange(item.value);
|
|
919
|
+
}}
|
|
920
|
+
orientation={orientation}
|
|
921
|
+
activationMode={activationMode}
|
|
922
|
+
className={className}
|
|
923
|
+
style={style}
|
|
924
|
+
>
|
|
925
|
+
{headerActions ? (
|
|
926
|
+
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
927
|
+
{tabList}
|
|
928
|
+
{headerActions}
|
|
929
|
+
</div>
|
|
930
|
+
) : (
|
|
931
|
+
tabList
|
|
932
|
+
)}
|
|
933
|
+
{items.map((item) => (
|
|
934
|
+
<TabsContent key={item.value} value={String(item.value)}>
|
|
935
|
+
{item.content}
|
|
936
|
+
</TabsContent>
|
|
937
|
+
))}
|
|
938
|
+
</DefaultTabsPrimitive>
|
|
939
|
+
);
|
|
940
|
+
};
|
|
912
941
|
|
|
913
942
|
export const defaultDesignSystemComponents: DesignSystemComponents = {
|
|
914
943
|
ActionButton: DefaultActionButton,
|
|
@@ -255,9 +255,11 @@ export interface DialogProps extends DesignSystemOverlayProps {
|
|
|
255
255
|
children: ReactNode;
|
|
256
256
|
description?: ReactNode;
|
|
257
257
|
footer?: ReactNode;
|
|
258
|
+
headerAction?: ReactNode;
|
|
258
259
|
trigger?: ReactElement;
|
|
259
|
-
size?: "small" | "medium" | "large" | "fullscreen";
|
|
260
|
+
size?: "small" | "medium" | "large" | "viewport" | "fullscreen";
|
|
260
261
|
dismissible?: boolean;
|
|
262
|
+
hideClose?: boolean;
|
|
261
263
|
closeLabel?: string;
|
|
262
264
|
initialFocusRef?: RefObject<HTMLElement | null>;
|
|
263
265
|
restoreFocusRef?: RefObject<HTMLElement | null>;
|
|
@@ -334,6 +336,7 @@ export interface TabsProps<Value extends DesignSystemKey = string>
|
|
|
334
336
|
onChange: (value: Value) => void;
|
|
335
337
|
orientation?: "horizontal" | "vertical";
|
|
336
338
|
activationMode?: "automatic" | "manual";
|
|
339
|
+
headerActions?: ReactNode;
|
|
337
340
|
}
|
|
338
341
|
|
|
339
342
|
export interface PickerComponent {
|
package/src/styles.css
CHANGED
package/src/ui/command.spec.tsx
CHANGED
|
@@ -1,7 +1,39 @@
|
|
|
1
1
|
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
2
3
|
import { describe, expect, it } from "vitest";
|
|
3
4
|
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
Command,
|
|
7
|
+
CommandDialog,
|
|
8
|
+
CommandInput,
|
|
9
|
+
CommandList,
|
|
10
|
+
MenuSearchInput,
|
|
11
|
+
} from "./command.js";
|
|
12
|
+
|
|
13
|
+
describe("integrated search semantics", () => {
|
|
14
|
+
it("uses a real searchbox for menus without dangling listbox relationships", () => {
|
|
15
|
+
const html = renderToStaticMarkup(
|
|
16
|
+
<MenuSearchInput aria-label="Search sources" />,
|
|
17
|
+
);
|
|
18
|
+
expect(html).toContain('role="searchbox"');
|
|
19
|
+
expect(html).not.toContain('role="combobox"');
|
|
20
|
+
expect(html).not.toContain("aria-controls");
|
|
21
|
+
expect(html).toContain("border-b");
|
|
22
|
+
});
|
|
23
|
+
it("preserves the existing CommandInput combobox and associated CommandList", () => {
|
|
24
|
+
const html = renderToStaticMarkup(
|
|
25
|
+
<Command>
|
|
26
|
+
<CommandInput />
|
|
27
|
+
<CommandList />
|
|
28
|
+
</Command>,
|
|
29
|
+
);
|
|
30
|
+
expect(html).toContain('role="combobox"');
|
|
31
|
+
expect(html).toContain('role="listbox"');
|
|
32
|
+
const controls = /aria-controls="([^"]+)"/.exec(html)?.[1];
|
|
33
|
+
expect(controls).toBeDefined();
|
|
34
|
+
expect(html).toContain(`id="${controls}"`);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
5
37
|
|
|
6
38
|
interface CommandDialogElement extends ReactElement {
|
|
7
39
|
props: {
|