@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,42 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { MenuSearchInput } from "../ui/command.js";
|
|
4
|
+
/** Integrated search with keyboard navigation owned by its Radix menu. */
|
|
5
|
+
export function ComposerContextMenuSearch({ placeholder, value, onValueChange, leading, invalid, onSubmit, }) {
|
|
6
|
+
const input = useRef(null);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const frame = requestAnimationFrame(() => {
|
|
9
|
+
const menu = input.current?.closest('[role="menu"]');
|
|
10
|
+
// Ancestors can mount in the same frame when a source is resumed.
|
|
11
|
+
if (!menu?.querySelector('[aria-haspopup="menu"][aria-expanded="true"]'))
|
|
12
|
+
input.current?.focus();
|
|
13
|
+
});
|
|
14
|
+
return () => cancelAnimationFrame(frame);
|
|
15
|
+
}, [placeholder]);
|
|
16
|
+
return (_jsx(MenuSearchInput, { ref: input, placeholder: placeholder, "aria-label": placeholder, "aria-invalid": invalid, value: value, onChange: (event) => onValueChange(event.target.value), leading: leading, onKeyDown: (event) => {
|
|
17
|
+
const menu = input.current?.closest('[role="menu"]');
|
|
18
|
+
const rows = Array.from(menu?.querySelectorAll('[role^="menuitem"]:not([data-disabled])') ?? []).filter((row) => row.closest('[role="menu"]') === menu);
|
|
19
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
20
|
+
event.preventDefault();
|
|
21
|
+
event.stopPropagation();
|
|
22
|
+
(event.key === "ArrowDown" ? rows[0] : rows.at(-1))?.focus();
|
|
23
|
+
}
|
|
24
|
+
else if (event.key === "Enter") {
|
|
25
|
+
event.preventDefault();
|
|
26
|
+
event.stopPropagation();
|
|
27
|
+
if (onSubmit)
|
|
28
|
+
onSubmit();
|
|
29
|
+
else
|
|
30
|
+
rows[0]?.click();
|
|
31
|
+
}
|
|
32
|
+
else if (event.key.length === 1 ||
|
|
33
|
+
event.key === "ArrowLeft" ||
|
|
34
|
+
event.key === "ArrowRight" ||
|
|
35
|
+
event.key === "Home" ||
|
|
36
|
+
event.key === "End") {
|
|
37
|
+
// Text entry must not activate Radix's menu typeahead.
|
|
38
|
+
event.stopPropagation();
|
|
39
|
+
}
|
|
40
|
+
} }));
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=ComposerContextMenuSearch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComposerContextMenuSearch.js","sourceRoot":"","sources":["../../src/composer/ComposerContextMenuSearch.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAkB,MAAM,OAAO,CAAC;AAE1D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,0EAA0E;AAC1E,MAAM,UAAU,yBAAyB,CAAC,EACxC,WAAW,EACX,KAAK,EACL,aAAa,EACb,OAAO,EACP,OAAO,EACP,QAAQ,GAQT;IACC,MAAM,KAAK,GAAG,MAAM,CAAmB,IAAI,CAAC,CAAC;IAC7C,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,KAAK,GAAG,qBAAqB,CAAC,GAAG,EAAE;YACvC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;YACrD,kEAAkE;YAClE,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,8CAA8C,CAAC;gBACtE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;QAC3B,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAClB,OAAO,CACL,KAAC,eAAe,IACd,GAAG,EAAE,KAAK,EACV,WAAW,EAAE,WAAW,gBACZ,WAAW,kBACT,OAAO,EACrB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EACtD,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;YACnB,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;YACrD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CACrB,IAAI,EAAE,gBAAgB,CACpB,yCAAyC,CAC1C,IAAI,EAAE,CACR,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC,CAAC;YACzD,IAAI,KAAK,CAAC,GAAG,KAAK,WAAW,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;gBACzD,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,KAAK,CAAC,eAAe,EAAE,CAAC;gBACxB,CAAC,KAAK,CAAC,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;YAC/D,CAAC;iBAAM,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;gBACjC,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,KAAK,CAAC,eAAe,EAAE,CAAC;gBACxB,IAAI,QAAQ;oBAAE,QAAQ,EAAE,CAAC;;oBACpB,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;YACxB,CAAC;iBAAM,IACL,KAAK,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC;gBACtB,KAAK,CAAC,GAAG,KAAK,WAAW;gBACzB,KAAK,CAAC,GAAG,KAAK,YAAY;gBAC1B,KAAK,CAAC,GAAG,KAAK,MAAM;gBACpB,KAAK,CAAC,GAAG,KAAK,KAAK,EACnB,CAAC;gBACD,uDAAuD;gBACvD,KAAK,CAAC,eAAe,EAAE,CAAC;YAC1B,CAAC;QACH,CAAC,GACD,CACH,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ComposerContextPickerConfig } from "./useComposerContextPicker.js";
|
|
2
|
+
export type { ComposerContextPickerConfig, ComposerContextPickerItem, ComposerContextPickerRequest, ComposerContextPickerResult, ComposerContextPickerSelection, ComposerContextPickerFooterAction, } from "./useComposerContextPicker.js";
|
|
3
|
+
export declare function ComposerContextPicker({ config, onClose, }: {
|
|
4
|
+
config: ComposerContextPickerConfig;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=ComposerContextPicker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComposerContextPicker.d.ts","sourceRoot":"","sources":["../../src/composer/ComposerContextPicker.tsx"],"names":[],"mappings":"AAUA,OAAO,EAEL,KAAK,2BAA2B,EACjC,MAAM,+BAA+B,CAAC;AACvC,YAAY,EACV,2BAA2B,EAC3B,yBAAyB,EACzB,4BAA4B,EAC5B,2BAA2B,EAC3B,8BAA8B,EAC9B,iCAAiC,GAClC,MAAM,+BAA+B,CAAC;AAEvC,wBAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,OAAO,GACR,EAAE;IACD,MAAM,EAAE,2BAA2B,CAAC;IACpC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,+BA0PA"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { IconArrowLeft, IconCheck } from "@tabler/icons-react";
|
|
3
|
+
import { Button } from "../ui/button.js";
|
|
4
|
+
import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, } from "../ui/dropdown-menu.js";
|
|
5
|
+
import { Skeleton } from "../ui/skeleton.js";
|
|
6
|
+
import { ComposerContextMenuSearch } from "./ComposerContextMenuSearch.js";
|
|
7
|
+
import { useComposerContextPicker, } from "./useComposerContextPicker.js";
|
|
8
|
+
export function ComposerContextPicker({ config, onClose, }) {
|
|
9
|
+
const { t, stage, link, setLink, location, cursors, move, back, select, submitLink, loading, error, items, hasMore, retry, selecting, actionError, setActionError, result, } = useComposerContextPicker({ config, onClose });
|
|
10
|
+
const check = (selected) => (_jsx("span", { className: "ms-auto size-4 shrink-0", "aria-hidden": "true", children: selected ? _jsx(IconCheck, { className: "size-4" }) : null }));
|
|
11
|
+
const footer = config.footerAction;
|
|
12
|
+
const footerContent = footer ? (_jsxs(_Fragment, { children: [footer.icon, footer.label] })) : null;
|
|
13
|
+
return (_jsxs(_Fragment, { children: [_jsx(ComposerContextMenuSearch, { leading: stage === "results" && config.link ? (_jsx(Button, { type: "button", variant: "ghost", size: "icon", "aria-label": t("agentChat.composer.contextBack", {
|
|
14
|
+
defaultValue: "Back",
|
|
15
|
+
}), onClick: back, children: _jsx(IconArrowLeft, { size: 16 }) })) : undefined, placeholder: stage === "link" ? config.link.placeholder : config.searchPlaceholder, invalid: stage === "link" && Boolean(actionError), value: stage === "link" ? link : location.search, onValueChange: (value) => {
|
|
16
|
+
if (stage === "link") {
|
|
17
|
+
setLink(value);
|
|
18
|
+
setActionError(undefined);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
cursors.current = [undefined];
|
|
22
|
+
move({ search: value, page: 1, url: location.url });
|
|
23
|
+
}
|
|
24
|
+
}, onSubmit: stage === "link" ? submitLink : undefined }), _jsx(DropdownMenuGroup, { className: "max-h-64 overflow-y-auto", "aria-busy": stage === "results" && (loading || selecting), children: stage === "link" ? (_jsxs(_Fragment, { children: [actionError && (_jsxs(_Fragment, { children: [_jsx("div", { role: "alert", className: "p-3 text-sm text-destructive", children: actionError.message }), actionError.retry && (_jsx(DropdownMenuItem, { disabled: selecting, onSelect: (event) => {
|
|
25
|
+
event.preventDefault();
|
|
26
|
+
actionError.retry?.();
|
|
27
|
+
}, children: t("agentChat.common.retry", { defaultValue: "Retry" }) }))] })), _jsx(DropdownMenuItem, { onSelect: (event) => {
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
submitLink();
|
|
30
|
+
}, children: config.link.submitLabel })] })) : (_jsxs(_Fragment, { children: [error ? (_jsxs(_Fragment, { children: [_jsx("div", { role: "alert", className: "p-3 text-sm text-destructive", children: error }), retry && (_jsx(DropdownMenuItem, { disabled: loading || selecting, onSelect: (event) => {
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
retry();
|
|
33
|
+
}, children: t("agentChat.common.retry", { defaultValue: "Retry" }) }))] })) : loading ? (_jsxs("div", { role: "status", "aria-label": t("agentChat.composer.contextPending", {
|
|
34
|
+
defaultValue: "Loading context…",
|
|
35
|
+
}), className: "grid gap-1 p-1", children: [_jsx(Skeleton, { className: "h-8 w-full" }), _jsx(Skeleton, { className: "h-8 w-full" }), _jsx(Skeleton, { className: "h-8 w-full" })] })) : (_jsxs(_Fragment, { children: [!items.length && (_jsx("div", { role: "status", className: "p-3 text-xs text-muted-foreground", children: config.emptyMessage ??
|
|
36
|
+
t("agentChat.composer.noContextResults", {
|
|
37
|
+
defaultValue: "No matching context.",
|
|
38
|
+
}) })), config.clearSelection && (_jsxs(DropdownMenuItem, { role: "menuitemcheckbox", "aria-checked": !config.selectedIds?.length, disabled: selecting, onSelect: (event) => {
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
void select();
|
|
41
|
+
}, children: [config.clearSelection.label, check(!config.selectedIds?.length)] })), items.map((item) => (_jsxs(DropdownMenuItem, { role: "menuitemcheckbox", "aria-checked": config.selectedIds?.includes(item.id) === true, disabled: item.disabled || selecting, onSelect: (event) => {
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
void select(item);
|
|
44
|
+
}, children: [_jsx("span", { className: "min-w-0 flex-1 truncate", children: item.title }), check(config.selectedIds?.includes(item.id) === true)] }, item.id)))] })), (location.page > 1 || hasMore) && (_jsxs(_Fragment, { children: [location.page > 1 && (_jsx(DropdownMenuItem, { disabled: loading || selecting, onSelect: (event) => {
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
move({
|
|
47
|
+
...location,
|
|
48
|
+
page: location.page - 1,
|
|
49
|
+
cursor: cursors.current[location.page - 2],
|
|
50
|
+
});
|
|
51
|
+
}, children: t("agentChat.composer.contextPrevious", {
|
|
52
|
+
defaultValue: "Previous",
|
|
53
|
+
}) })), hasMore && (_jsx(DropdownMenuItem, { disabled: loading || selecting || Boolean(error), onSelect: (event) => {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
cursors.current = [
|
|
56
|
+
...cursors.current.slice(0, location.page),
|
|
57
|
+
result?.nextCursor,
|
|
58
|
+
];
|
|
59
|
+
move({
|
|
60
|
+
...location,
|
|
61
|
+
page: location.page + 1,
|
|
62
|
+
cursor: result?.nextCursor,
|
|
63
|
+
});
|
|
64
|
+
}, children: t("agentChat.composer.contextNext", {
|
|
65
|
+
defaultValue: "Next",
|
|
66
|
+
}) }))] }))] })) }), footer && (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuGroup, { children: footer.renderLink && !footer.disabled && !selecting ? (_jsx(DropdownMenuItem, { asChild: true, onSelect: onClose, children: footer.renderLink(footerContent) })) : (_jsx(DropdownMenuItem, { disabled: footer.disabled || selecting, onSelect: (event) => {
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
void select("footer");
|
|
69
|
+
}, children: footerContent })) })] }))] }));
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=ComposerContextPicker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComposerContextPicker.js","sourceRoot":"","sources":["../../src/composer/ComposerContextPicker.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComposerContextPicker.spec.d.ts","sourceRoot":"","sources":["../../src/composer/ComposerContextPicker.spec.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// @vitest-environment happy-dom
|
|
3
|
+
import { act } from "react";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { TooltipProvider } from "../ui/tooltip.js";
|
|
7
|
+
import { ComposerContextMenu, } from "./ComposerContextMenu.js";
|
|
8
|
+
let container;
|
|
9
|
+
let root;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
12
|
+
vi.spyOn(HTMLElement.prototype, "scrollIntoView").mockImplementation(() => { });
|
|
13
|
+
container = document.createElement("div");
|
|
14
|
+
document.body.append(container);
|
|
15
|
+
root = createRoot(container);
|
|
16
|
+
});
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
await act(async () => root.unmount());
|
|
19
|
+
container.remove();
|
|
20
|
+
vi.restoreAllMocks();
|
|
21
|
+
vi.unstubAllGlobals();
|
|
22
|
+
});
|
|
23
|
+
async function render(config) {
|
|
24
|
+
await act(async () => root.render(_jsx(TooltipProvider, { children: _jsx(ComposerContextMenu, { items: [
|
|
25
|
+
{
|
|
26
|
+
id: "category",
|
|
27
|
+
label: "Category",
|
|
28
|
+
searchPlaceholder: "Search category",
|
|
29
|
+
children: [{ id: "source", label: "Source", picker: config }],
|
|
30
|
+
},
|
|
31
|
+
] }) })));
|
|
32
|
+
}
|
|
33
|
+
const menus = () => Array.from(document.querySelectorAll('[role="menu"]'));
|
|
34
|
+
const row = (label) => {
|
|
35
|
+
const found = Array.from(document.querySelectorAll('[role^="menuitem"]')).find((element) => element.textContent === label);
|
|
36
|
+
expect(found, label).toBeDefined();
|
|
37
|
+
return found;
|
|
38
|
+
};
|
|
39
|
+
async function key(target, key) {
|
|
40
|
+
await act(async () => {
|
|
41
|
+
target.focus();
|
|
42
|
+
target.dispatchEvent(new KeyboardEvent("keydown", { key, bubbles: true, cancelable: true }));
|
|
43
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async function click(label) {
|
|
47
|
+
await act(async () => row(label).click());
|
|
48
|
+
}
|
|
49
|
+
async function open() {
|
|
50
|
+
await key(container.querySelector('button[aria-label="Add context"]'), "ArrowDown");
|
|
51
|
+
await click("Add context");
|
|
52
|
+
await click("Category");
|
|
53
|
+
await click("Source");
|
|
54
|
+
}
|
|
55
|
+
async function search(value, placeholder = "Search source") {
|
|
56
|
+
const input = document.querySelector(`input[placeholder="${placeholder}"]`);
|
|
57
|
+
expect(input).not.toBeNull();
|
|
58
|
+
await act(async () => {
|
|
59
|
+
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value").set.call(input, value);
|
|
60
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function deferred() {
|
|
64
|
+
let resolve;
|
|
65
|
+
let reject;
|
|
66
|
+
const promise = new Promise((yes, no) => {
|
|
67
|
+
resolve = yes;
|
|
68
|
+
reject = no;
|
|
69
|
+
});
|
|
70
|
+
return { promise, resolve, reject };
|
|
71
|
+
}
|
|
72
|
+
const local = () => ({
|
|
73
|
+
searchPlaceholder: "Search source",
|
|
74
|
+
items: [
|
|
75
|
+
{ id: "one", title: "One" },
|
|
76
|
+
{ id: "two", title: "Two" },
|
|
77
|
+
],
|
|
78
|
+
onSelect: vi.fn(),
|
|
79
|
+
});
|
|
80
|
+
describe("declarative context picker", () => {
|
|
81
|
+
it("disables page controls while fetching and retains the footer", async () => {
|
|
82
|
+
const pending = deferred();
|
|
83
|
+
const load = vi
|
|
84
|
+
.fn()
|
|
85
|
+
.mockResolvedValueOnce({
|
|
86
|
+
items: [{ id: "one", title: "One" }],
|
|
87
|
+
hasMore: true,
|
|
88
|
+
nextCursor: "next",
|
|
89
|
+
})
|
|
90
|
+
.mockReturnValueOnce(pending.promise);
|
|
91
|
+
await render({
|
|
92
|
+
searchPlaceholder: "Search source",
|
|
93
|
+
load,
|
|
94
|
+
onSelect: vi.fn(),
|
|
95
|
+
footerAction: { label: "Create source", onSelect: () => false },
|
|
96
|
+
});
|
|
97
|
+
await open();
|
|
98
|
+
await click("Next");
|
|
99
|
+
expect(row("Next").getAttribute("aria-disabled")).toBe("true");
|
|
100
|
+
expect(row("Previous").getAttribute("aria-disabled")).toBe("true");
|
|
101
|
+
expect(row("Create source").getAttribute("aria-disabled")).not.toBe("true");
|
|
102
|
+
expect(menus().at(-1)?.querySelectorAll('[role="status"]')).toHaveLength(1);
|
|
103
|
+
expect(menus().at(-1)?.textContent).not.toContain("No matching context.");
|
|
104
|
+
await act(async () => pending.resolve({ items: [], hasMore: false }));
|
|
105
|
+
expect(menus().at(-1)?.textContent).toContain("No matching context.");
|
|
106
|
+
});
|
|
107
|
+
it("reports malformed remote payloads instead of turning them into empty lists", async () => {
|
|
108
|
+
await render({
|
|
109
|
+
searchPlaceholder: "Search source",
|
|
110
|
+
load: vi.fn().mockResolvedValue({ hasMore: false }),
|
|
111
|
+
onSelect: vi.fn(),
|
|
112
|
+
});
|
|
113
|
+
await open();
|
|
114
|
+
expect(menus().at(-1)?.textContent).toContain("Could not load context.");
|
|
115
|
+
expect(menus().at(-1)?.querySelector('[role="status"]')).toBeNull();
|
|
116
|
+
expect(row("Retry")).toBeDefined();
|
|
117
|
+
});
|
|
118
|
+
it("supports local error Retry, disabled rows and clear selection", async () => {
|
|
119
|
+
const retry = vi.fn();
|
|
120
|
+
const clear = vi.fn().mockResolvedValue(false);
|
|
121
|
+
const select = vi.fn();
|
|
122
|
+
const config = {
|
|
123
|
+
searchPlaceholder: "Search source",
|
|
124
|
+
items: [{ id: "one", title: "One", disabled: true }],
|
|
125
|
+
error: "Local read failed",
|
|
126
|
+
onRetry: retry,
|
|
127
|
+
onSelect: select,
|
|
128
|
+
selectedIds: ["one"],
|
|
129
|
+
clearSelection: { label: "Clear selection", onSelect: clear },
|
|
130
|
+
};
|
|
131
|
+
await render(config);
|
|
132
|
+
await open();
|
|
133
|
+
await click("Retry");
|
|
134
|
+
expect(retry).toHaveBeenCalledOnce();
|
|
135
|
+
await render({ ...config, error: undefined });
|
|
136
|
+
await click("One");
|
|
137
|
+
expect(select).not.toHaveBeenCalled();
|
|
138
|
+
await click("Clear selection");
|
|
139
|
+
expect(clear).toHaveBeenCalledOnce();
|
|
140
|
+
expect(menus()).toHaveLength(4);
|
|
141
|
+
});
|
|
142
|
+
it("retries a failed selection and closes only on success", async () => {
|
|
143
|
+
const onSelect = vi
|
|
144
|
+
.fn()
|
|
145
|
+
.mockRejectedValueOnce(new Error("Reference read failed"))
|
|
146
|
+
.mockResolvedValue(undefined);
|
|
147
|
+
await render({ ...local(), onSelect });
|
|
148
|
+
await open();
|
|
149
|
+
await click("One");
|
|
150
|
+
expect(menus().at(-1)?.textContent).toContain("Reference read failed");
|
|
151
|
+
await click("Retry");
|
|
152
|
+
expect(onSelect).toHaveBeenCalledTimes(2);
|
|
153
|
+
expect(menus()).toHaveLength(0);
|
|
154
|
+
});
|
|
155
|
+
it("aborts selection on identity change and cannot close the new account's picker", async () => {
|
|
156
|
+
const pending = deferred();
|
|
157
|
+
const onSelect = vi.fn().mockReturnValue(pending.promise);
|
|
158
|
+
const config = { ...local(), onSelect, scopeKey: "account-a" };
|
|
159
|
+
await render(config);
|
|
160
|
+
await open();
|
|
161
|
+
await click("One");
|
|
162
|
+
await render({ ...config, scopeKey: "account-b" });
|
|
163
|
+
expect(onSelect.mock.calls[0][1].signal.aborted).toBe(true);
|
|
164
|
+
await act(async () => pending.resolve());
|
|
165
|
+
expect(menus()).toHaveLength(4);
|
|
166
|
+
});
|
|
167
|
+
it("keeps link, search and page on sync refresh, and aborts a dismissed load", async () => {
|
|
168
|
+
const pending = deferred();
|
|
169
|
+
const load = vi.fn().mockResolvedValue({
|
|
170
|
+
items: [{ id: "one", title: "One" }],
|
|
171
|
+
hasMore: true,
|
|
172
|
+
nextCursor: "next",
|
|
173
|
+
});
|
|
174
|
+
const config = {
|
|
175
|
+
searchPlaceholder: "Search source",
|
|
176
|
+
load,
|
|
177
|
+
onSelect: vi.fn(),
|
|
178
|
+
refreshKey: 1,
|
|
179
|
+
link: { placeholder: "Source URL", submitLabel: "Read link" },
|
|
180
|
+
};
|
|
181
|
+
await render(config);
|
|
182
|
+
await open();
|
|
183
|
+
await search("https://example.com/source", "Source URL");
|
|
184
|
+
await click("Read link");
|
|
185
|
+
await search("one");
|
|
186
|
+
await click("Next");
|
|
187
|
+
load.mockReturnValueOnce(pending.promise);
|
|
188
|
+
await render({ ...config, refreshKey: 2 });
|
|
189
|
+
const request = load.mock.calls.at(-1)[0];
|
|
190
|
+
expect(request).toMatchObject({
|
|
191
|
+
page: 2,
|
|
192
|
+
search: "one",
|
|
193
|
+
url: "https://example.com/source",
|
|
194
|
+
cursor: "next",
|
|
195
|
+
});
|
|
196
|
+
await key(document.querySelector('input[placeholder="Search source"]'), "Escape");
|
|
197
|
+
expect(request.signal.aborted).toBe(true);
|
|
198
|
+
await act(async () => pending.resolve({ items: [{ id: "late", title: "Late" }] }));
|
|
199
|
+
expect(menus()).toHaveLength(3);
|
|
200
|
+
expect(document.body.textContent).not.toContain("Late");
|
|
201
|
+
});
|
|
202
|
+
it("keeps connected panels, integrated headers and source search autofocus", async () => {
|
|
203
|
+
await render(local());
|
|
204
|
+
await open();
|
|
205
|
+
expect(menus()).toHaveLength(4);
|
|
206
|
+
expect(menus().every((menu) => menu.classList.contains("w-64"))).toBe(true);
|
|
207
|
+
const searches = document.querySelectorAll('input[role="searchbox"]');
|
|
208
|
+
expect(searches).toHaveLength(4);
|
|
209
|
+
expect(document.querySelector('[role="combobox"]')).toBeNull();
|
|
210
|
+
for (const input of searches)
|
|
211
|
+
expect(input.hasAttribute("aria-controls")).toBe(false);
|
|
212
|
+
await act(async () => {
|
|
213
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
214
|
+
});
|
|
215
|
+
expect(document.activeElement).toBe(document.querySelector('input[placeholder="Search source"]'));
|
|
216
|
+
});
|
|
217
|
+
it("filters locally, reserves trailing checks, and preserves selection when callback returns false", async () => {
|
|
218
|
+
const select = vi.fn().mockResolvedValue(false);
|
|
219
|
+
await render({ ...local(), selectedIds: ["one"], onSelect: select });
|
|
220
|
+
await open();
|
|
221
|
+
expect(row("One").getAttribute("aria-checked")).toBe("true");
|
|
222
|
+
expect(row("Two").lastElementChild?.classList.contains("size-4")).toBe(true);
|
|
223
|
+
await search("two");
|
|
224
|
+
expect(menus().at(-1)?.textContent).not.toContain("One");
|
|
225
|
+
await click("Two");
|
|
226
|
+
expect(select).toHaveBeenCalledWith({ id: "two", title: "Two" }, expect.objectContaining({
|
|
227
|
+
search: "two",
|
|
228
|
+
page: 1,
|
|
229
|
+
signal: expect.any(AbortSignal),
|
|
230
|
+
}));
|
|
231
|
+
expect(menus()).toHaveLength(4);
|
|
232
|
+
});
|
|
233
|
+
it("routes typing and arrows through real Radix menu items and closes only the deepest submenu", async () => {
|
|
234
|
+
await render(local());
|
|
235
|
+
await open();
|
|
236
|
+
const input = document.querySelector('input[placeholder="Search source"]');
|
|
237
|
+
await key(input, "t");
|
|
238
|
+
expect(document.activeElement).toBe(input);
|
|
239
|
+
await key(input, "ArrowDown");
|
|
240
|
+
expect(document.activeElement).toBe(row("One"));
|
|
241
|
+
await key(row("One"), "ArrowDown");
|
|
242
|
+
expect(document.activeElement).toBe(row("Two"));
|
|
243
|
+
await key(row("Two"), "ArrowLeft");
|
|
244
|
+
expect(menus()).toHaveLength(3);
|
|
245
|
+
expect(document.activeElement).toBe(row("Source"));
|
|
246
|
+
await key(row("Source"), "Escape");
|
|
247
|
+
expect(menus()).toHaveLength(2);
|
|
248
|
+
expect(document.activeElement).toBe(row("Category"));
|
|
249
|
+
});
|
|
250
|
+
it("loads pages with cursor history and preserves location on refresh without descriptor loops", async () => {
|
|
251
|
+
const load = vi
|
|
252
|
+
.fn()
|
|
253
|
+
.mockResolvedValue({
|
|
254
|
+
items: [{ id: "one", title: "One" }],
|
|
255
|
+
hasMore: true,
|
|
256
|
+
nextCursor: "cursor-2",
|
|
257
|
+
});
|
|
258
|
+
const config = {
|
|
259
|
+
searchPlaceholder: "Search source",
|
|
260
|
+
load,
|
|
261
|
+
onSelect: vi.fn(),
|
|
262
|
+
refreshKey: 0,
|
|
263
|
+
};
|
|
264
|
+
await render(config);
|
|
265
|
+
await open();
|
|
266
|
+
expect(load).toHaveBeenCalledTimes(1);
|
|
267
|
+
await render({ ...config });
|
|
268
|
+
expect(load).toHaveBeenCalledTimes(1);
|
|
269
|
+
await click("Next");
|
|
270
|
+
expect(load.mock.calls.at(-1)?.[0]).toMatchObject({
|
|
271
|
+
page: 2,
|
|
272
|
+
cursor: "cursor-2",
|
|
273
|
+
search: "",
|
|
274
|
+
});
|
|
275
|
+
await render({ ...config, refreshKey: 1 });
|
|
276
|
+
expect(load.mock.calls.at(-1)?.[0]).toMatchObject({
|
|
277
|
+
page: 2,
|
|
278
|
+
cursor: "cursor-2",
|
|
279
|
+
});
|
|
280
|
+
await click("Previous");
|
|
281
|
+
expect(load.mock.calls.at(-1)?.[0].page).toBe(1);
|
|
282
|
+
await search("find");
|
|
283
|
+
expect(load.mock.calls.at(-1)?.[0]).toMatchObject({
|
|
284
|
+
search: "find",
|
|
285
|
+
page: 1,
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
it("shows exclusive loading/error/empty states and real Retry", async () => {
|
|
289
|
+
const pending = deferred();
|
|
290
|
+
const load = vi
|
|
291
|
+
.fn()
|
|
292
|
+
.mockReturnValueOnce(pending.promise)
|
|
293
|
+
.mockResolvedValue({ items: [], hasMore: false });
|
|
294
|
+
await render({
|
|
295
|
+
searchPlaceholder: "Search source",
|
|
296
|
+
load,
|
|
297
|
+
onSelect: vi.fn(),
|
|
298
|
+
});
|
|
299
|
+
await open();
|
|
300
|
+
const source = () => menus().at(-1);
|
|
301
|
+
expect(source().querySelectorAll('[role="status"]')).toHaveLength(1);
|
|
302
|
+
expect(source().textContent).not.toContain("No matching context.");
|
|
303
|
+
await act(async () => pending.reject(new Error("Read failed")));
|
|
304
|
+
expect(source().querySelectorAll('[role="alert"]')).toHaveLength(1);
|
|
305
|
+
expect(source().querySelector('[role="status"]')).toBeNull();
|
|
306
|
+
await click("Retry");
|
|
307
|
+
expect(source().querySelector('[role="alert"]')).toBeNull();
|
|
308
|
+
expect(source().textContent).toContain("No matching context.");
|
|
309
|
+
});
|
|
310
|
+
it("suppresses stale search and identity responses even when loaders ignore abort", async () => {
|
|
311
|
+
const first = deferred();
|
|
312
|
+
const second = deferred();
|
|
313
|
+
const load = vi
|
|
314
|
+
.fn()
|
|
315
|
+
.mockReturnValueOnce(first.promise)
|
|
316
|
+
.mockReturnValueOnce(second.promise)
|
|
317
|
+
.mockResolvedValue({ items: [{ id: "safe", title: "Safe" }] });
|
|
318
|
+
const config = {
|
|
319
|
+
searchPlaceholder: "Search source",
|
|
320
|
+
load,
|
|
321
|
+
onSelect: vi.fn(),
|
|
322
|
+
scopeKey: "account-a",
|
|
323
|
+
};
|
|
324
|
+
await render(config);
|
|
325
|
+
await open();
|
|
326
|
+
await search("new");
|
|
327
|
+
expect(load.mock.calls[0][0].signal.aborted).toBe(true);
|
|
328
|
+
await render({ ...config, scopeKey: "account-b" });
|
|
329
|
+
expect(load.mock.calls.at(-1)?.[0].search).toBe("");
|
|
330
|
+
await act(async () => {
|
|
331
|
+
first.resolve({ items: [{ id: "old", title: "Old" }] });
|
|
332
|
+
second.resolve({ items: [{ id: "leak", title: "Leak" }] });
|
|
333
|
+
});
|
|
334
|
+
expect(menus().at(-1)?.textContent).toContain("Safe");
|
|
335
|
+
expect(menus().at(-1)?.textContent).not.toMatch(/Old|Leak/);
|
|
336
|
+
});
|
|
337
|
+
it("ignores a slow selection after dismiss and reopening", async () => {
|
|
338
|
+
const pending = deferred();
|
|
339
|
+
const select = vi.fn().mockReturnValue(pending.promise);
|
|
340
|
+
await render({ ...local(), onSelect: select });
|
|
341
|
+
await open();
|
|
342
|
+
await click("One");
|
|
343
|
+
const signal = select.mock.calls[0][1].signal;
|
|
344
|
+
await key(row("One"), "Escape");
|
|
345
|
+
expect(signal.aborted).toBe(true);
|
|
346
|
+
await click("Source");
|
|
347
|
+
await act(async () => pending.resolve());
|
|
348
|
+
expect(menus()).toHaveLength(4);
|
|
349
|
+
});
|
|
350
|
+
it("validates links before reading, searches results, and returns to the populated link stage", async () => {
|
|
351
|
+
const load = vi
|
|
352
|
+
.fn()
|
|
353
|
+
.mockResolvedValue({ items: [{ id: "one", title: "One" }] });
|
|
354
|
+
await render({
|
|
355
|
+
searchPlaceholder: "Search source",
|
|
356
|
+
load,
|
|
357
|
+
onSelect: vi.fn(),
|
|
358
|
+
link: {
|
|
359
|
+
placeholder: "Source URL",
|
|
360
|
+
submitLabel: "Read link",
|
|
361
|
+
validate: (url) => url.startsWith("https://") ? undefined : "Use HTTPS",
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
await open();
|
|
365
|
+
expect(load).not.toHaveBeenCalled();
|
|
366
|
+
await click("Read link");
|
|
367
|
+
expect(menus().at(-1)?.textContent).toContain("Enter a link.");
|
|
368
|
+
await search("bad", "Source URL");
|
|
369
|
+
await click("Read link");
|
|
370
|
+
expect(menus().at(-1)?.textContent).toContain("Use HTTPS");
|
|
371
|
+
await search("https://example.com/source", "Source URL");
|
|
372
|
+
await click("Read link");
|
|
373
|
+
expect(load.mock.calls[0][0]).toMatchObject({
|
|
374
|
+
url: "https://example.com/source",
|
|
375
|
+
page: 1,
|
|
376
|
+
});
|
|
377
|
+
await search("one");
|
|
378
|
+
expect(load.mock.calls.at(-1)?.[0]).toMatchObject({
|
|
379
|
+
url: "https://example.com/source",
|
|
380
|
+
search: "one",
|
|
381
|
+
});
|
|
382
|
+
await act(async () => document
|
|
383
|
+
.querySelector('button[aria-label="Back"]')
|
|
384
|
+
.click());
|
|
385
|
+
expect(document.querySelector('input[placeholder="Source URL"]')?.value).toBe("https://example.com/source");
|
|
386
|
+
expect(menus()).toHaveLength(4);
|
|
387
|
+
await key(document.querySelector('input[placeholder="Source URL"]'), "ArrowLeft");
|
|
388
|
+
expect(menus()).toHaveLength(4);
|
|
389
|
+
await key(document.querySelector('input[placeholder="Source URL"]'), "ArrowRight");
|
|
390
|
+
expect(menus()).toHaveLength(4);
|
|
391
|
+
await key(row("Read link"), "ArrowLeft");
|
|
392
|
+
expect(menus()).toHaveLength(3);
|
|
393
|
+
});
|
|
394
|
+
it("keeps a genuine link footer present in empty and populated states", async () => {
|
|
395
|
+
const config = {
|
|
396
|
+
...local(),
|
|
397
|
+
items: [],
|
|
398
|
+
footerAction: {
|
|
399
|
+
label: "Create source",
|
|
400
|
+
renderLink: (children) => (_jsx("a", { href: "/sources/setup", children: children })),
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
await render(config);
|
|
404
|
+
await open();
|
|
405
|
+
const footer = row("Create source");
|
|
406
|
+
expect(footer.tagName).toBe("A");
|
|
407
|
+
expect(footer.getAttribute("href")).toBe("/sources/setup");
|
|
408
|
+
expect(footer.closest('[role="group"]')).not.toBeNull();
|
|
409
|
+
expect(menus().at(-1)?.querySelector('[role="separator"]')).not.toBeNull();
|
|
410
|
+
await render({ ...config, items: [{ id: "one", title: "One" }] });
|
|
411
|
+
expect(row("Create source")).toBe(footer);
|
|
412
|
+
});
|
|
413
|
+
it.each(["meta", "ctrl", "middle"])("preserves native %s-click link activation", async (modifier) => {
|
|
414
|
+
await render({
|
|
415
|
+
...local(),
|
|
416
|
+
footerAction: {
|
|
417
|
+
label: "Create source",
|
|
418
|
+
renderLink: (children) => _jsx("a", { href: "/sources/setup", children: children }),
|
|
419
|
+
},
|
|
420
|
+
});
|
|
421
|
+
await open();
|
|
422
|
+
const event = new MouseEvent(modifier === "middle" ? "auxclick" : "click", {
|
|
423
|
+
bubbles: true,
|
|
424
|
+
cancelable: true,
|
|
425
|
+
metaKey: modifier === "meta",
|
|
426
|
+
ctrlKey: modifier === "ctrl",
|
|
427
|
+
button: modifier === "middle" ? 1 : 0,
|
|
428
|
+
});
|
|
429
|
+
await act(async () => row("Create source").dispatchEvent(event));
|
|
430
|
+
expect(event.defaultPrevented).toBe(false);
|
|
431
|
+
});
|
|
432
|
+
it("retries footer failure from the link stage without starting a read", async () => {
|
|
433
|
+
const footer = vi
|
|
434
|
+
.fn()
|
|
435
|
+
.mockRejectedValueOnce(new Error("Could not open setup"))
|
|
436
|
+
.mockResolvedValue(false);
|
|
437
|
+
const load = vi.fn();
|
|
438
|
+
await render({
|
|
439
|
+
searchPlaceholder: "Search source",
|
|
440
|
+
load,
|
|
441
|
+
onSelect: vi.fn(),
|
|
442
|
+
link: { placeholder: "Source URL", submitLabel: "Read link" },
|
|
443
|
+
footerAction: { label: "Create source", onSelect: footer },
|
|
444
|
+
});
|
|
445
|
+
await open();
|
|
446
|
+
await click("Create source");
|
|
447
|
+
expect(menus().at(-1)?.textContent).toContain("Could not open setup");
|
|
448
|
+
await click("Retry");
|
|
449
|
+
expect(footer).toHaveBeenCalledTimes(2);
|
|
450
|
+
expect(load).not.toHaveBeenCalled();
|
|
451
|
+
expect(menus()).toHaveLength(4);
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
//# sourceMappingURL=ComposerContextPicker.spec.js.map
|