Skip to content

Commit 780e7f0

Browse files
committed
Let empty mention dropdown release Enter
1 parent e798075 commit 780e7f0

3 files changed

Lines changed: 57 additions & 11 deletions

File tree

client/src/components/ChatGXY/ChatInput.test.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,21 @@ vi.mock("@/stores/historyItemsStore", () => ({
1010
useHistoryItemsStore: () => ({ getHistoryItems: () => [] }),
1111
}));
1212

13-
function mountInput(props: Record<string, unknown> = {}) {
13+
function mountInput(
14+
props: Record<string, unknown> = {},
15+
stubs: Record<string, boolean> = {
16+
FontAwesomeIcon: true,
17+
LoadingSpan: true,
18+
MentionDropdown: true,
19+
},
20+
) {
1421
return mount(ChatInput as any, {
1522
propsData: {
1623
value: "",
1724
busy: false,
1825
...props,
1926
},
20-
stubs: {
21-
FontAwesomeIcon: true,
22-
LoadingSpan: true,
23-
MentionDropdown: true,
24-
},
27+
stubs,
2528
});
2629
}
2730

@@ -114,6 +117,36 @@ describe("ChatInput", () => {
114117
await wrapper.find("textarea").trigger("keydown.enter", { shiftKey: true });
115118
expect(wrapper.emitted("submit")).toBeFalsy();
116119
});
120+
121+
it("emits submit on Enter when the mention dropdown has no matches", async () => {
122+
const value = "@dataset:nope";
123+
const wrapper = mountInput({ value }, { FontAwesomeIcon: true, LoadingSpan: true });
124+
const textarea = wrapper.find("textarea");
125+
const el = textarea.element as HTMLTextAreaElement;
126+
el.selectionStart = value.length;
127+
el.selectionEnd = value.length;
128+
129+
await textarea.trigger("input");
130+
await textarea.trigger("keydown.enter");
131+
132+
expect(wrapper.emitted("submit")).toBeTruthy();
133+
});
134+
135+
it("closes an empty mention dropdown on Escape", async () => {
136+
const value = "@dataset:nope";
137+
const wrapper = mountInput({ value }, { FontAwesomeIcon: true, LoadingSpan: true });
138+
const textarea = wrapper.find("textarea");
139+
const el = textarea.element as HTMLTextAreaElement;
140+
el.selectionStart = value.length;
141+
el.selectionEnd = value.length;
142+
143+
await textarea.trigger("input");
144+
expect(wrapper.find(".mention-dropdown").attributes("style") ?? "").not.toContain("display: none");
145+
146+
await textarea.trigger("keydown", { key: "Escape" });
147+
148+
expect(wrapper.find(".mention-dropdown").attributes("style") ?? "").toContain("display: none");
149+
});
117150
});
118151

119152
describe("busy state UI", () => {

client/src/components/ChatGXY/ChatInput.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ const DROPDOWN_KEYS = new Set(["ArrowDown", "ArrowUp", "Tab", "Escape", "Enter"]
4545
function onKeydown(event: KeyboardEvent) {
4646
if (mentionTrigger.value && dropdownRef.value && DROPDOWN_KEYS.has(event.key)) {
4747
if (event.key !== "Enter" || !event.shiftKey) {
48-
dropdownRef.value.handleKeydown(event);
49-
return;
48+
const handled = dropdownRef.value.handleKeydown(event);
49+
if (handled) {
50+
return;
51+
}
5052
}
5153
}
5254

client/src/components/ChatGXY/MentionDropdown.vue

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,32 +140,43 @@ function handleSelect(item: MenuItem) {
140140
}
141141
}
142142
143-
function handleKeydown(event: KeyboardEvent) {
143+
function handleKeydown(event: KeyboardEvent): boolean {
144144
if (!props.visible) {
145-
return;
145+
return false;
146146
}
147147
148148
if (menuItems.value.length === 0) {
149-
return;
149+
if (event.key === "Escape") {
150+
event.preventDefault();
151+
emit("close");
152+
return true;
153+
}
154+
return false;
150155
}
151156
152157
if (event.key === "ArrowDown") {
153158
event.preventDefault();
154159
selectedIndex.value = (selectedIndex.value + 1) % menuItems.value.length;
160+
return true;
155161
} else if (event.key === "ArrowUp") {
156162
event.preventDefault();
157163
selectedIndex.value = (selectedIndex.value - 1 + menuItems.value.length) % menuItems.value.length;
164+
return true;
158165
} else if (event.key === "Enter" || event.key === "Tab") {
159166
event.preventDefault();
160167
event.stopPropagation();
161168
const item = menuItems.value[selectedIndex.value];
162169
if (item) {
163170
handleSelect(item);
164171
}
172+
return true;
165173
} else if (event.key === "Escape") {
166174
event.preventDefault();
167175
emit("close");
176+
return true;
168177
}
178+
179+
return false;
169180
}
170181
171182
function updatePosition() {

0 commit comments

Comments
 (0)