Skip to content

Commit 65a982e

Browse files
committed
Frontend: Drop chip-selector blur commit hook entirely photoprism#4966
The previous fix (a81082a) kept @blur="onInputBlur" with a relatedTarget guard so ArrowDown navigation wouldn't commit typed prefixes. In practice the blur hook had no reachable trigger left once that guard was in place — neither Tab nor click-outside actually fired the commit. Drop the @blur binding and the onInputBlur helper entirely; commits now happen only on Enter (`onEnter`) or when v-combobox emits a real item-object selection (`onComboboxChange`). Pending typed text is discarded when focus leaves the field, which matches how the sidebar combobox already behaves. Test pin rewritten to assert both halves of the contract: ArrowDown does not commit, Enter still does.
1 parent b06483c commit 65a982e

2 files changed

Lines changed: 17 additions & 63 deletions

File tree

frontend/src/component/input/chip-selector.vue

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
class="chip-selector__input"
5252
@click:control="focusInput"
5353
@keydown.enter.prevent="onEnter"
54-
@blur="onInputBlur"
5554
@update:model-value="onComboboxChange"
5655
@update:menu="onMenuUpdate"
5756
>
@@ -333,25 +332,6 @@ export default {
333332
this.addNewItem();
334333
},
335334
336-
// Blur on the combobox input fires for two distinct reasons:
337-
// (a) the user is leaving the field (Tab, click elsewhere) — pending
338-
// text should commit as a new chip, mirroring the inline-text
339-
// auto-commit-on-blur contract elsewhere in the app.
340-
// (b) Vuetify shifts DOM focus into the dropdown menu (ArrowDown,
341-
// Home, End) — the user is mid-navigation; committing the
342-
// typed prefix here would silently turn `ca` into a brand-new
343-
// chip before they can pick `Camping` from the suggestions.
344-
// The menu list items are teleported to a v-overlay-container, so the
345-
// shared DOM parent is `<body>`; detect them via `relatedTarget` class
346-
// / ancestor lookup and skip the commit in case (b).
347-
onInputBlur(event) {
348-
const rt = event?.relatedTarget;
349-
if (rt && (rt.classList?.contains("v-list-item") || rt.closest?.(".v-overlay-container, .v-list, .v-menu"))) {
350-
return;
351-
}
352-
this.addNewItem();
353-
},
354-
355335
onMenuUpdate(val) {
356336
if (val && this.suppressMenuOpen) {
357337
this.menuOpen = false;

frontend/tests/vitest/component/file/chip-selector.test.js

Lines changed: 17 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -233,56 +233,30 @@ describe("component/file/chip-selector", () => {
233233
expect(wrapper.emitted("update:items")).toBeFalsy();
234234
});
235235

236-
// Repros the user-reported regression: typing `ca` and pressing ArrowDown
237-
// used to commit `ca` as a brand-new chip before the user could pick
238-
// `Camping` from the dropdown. The trigger is `@blur` firing on the
239-
// input as Vuetify shifts DOM focus into the teleported v-list-item
240-
// menu. onInputBlur skips the commit when relatedTarget is a menu item.
241-
it("skips addNewItem on blur when focus shifts into the dropdown menu (ArrowDown)", () => {
242-
wrapper.vm.newItemTitle = "ca";
243-
244-
// relatedTarget mimics the v-list-item Vuetify focuses on ArrowDown.
245-
const menuItem = document.createElement("div");
246-
menuItem.className = "v-list-item v-list-item--link";
247-
248-
wrapper.vm.onInputBlur({ relatedTarget: menuItem });
249-
250-
expect(wrapper.emitted("update:items")).toBeFalsy();
251-
});
236+
// Regression pin for the user-reported ArrowDown bug: previously
237+
// pressing ↓ shifted DOM focus to the first v-list-item in the
238+
// dropdown menu and the input's @blur handler treated that focus
239+
// shift as "commit pending text", silently turning typed prefixes
240+
// (e.g. `ca`) into a brand-new chip before the user could highlight
241+
// `Camping` and press Enter. The fix is to drop the @blur handler
242+
// entirely: commits only happen on Enter (`onEnter`) or on the
243+
// combobox emitting a real item-object selection
244+
// (`onComboboxChange`). Tabbing or clicking elsewhere with pending
245+
// text simply discards it. This test pins both halves of that
246+
// contract — Enter still commits, ArrowDown does not.
247+
it("commits typed text on Enter but not on ArrowDown", async () => {
248+
const combobox = wrapper.findComponent({ name: "VCombobox" });
252249

253-
it("skips addNewItem on blur when relatedTarget is inside a v-overlay-container", () => {
254250
wrapper.vm.newItemTitle = "ca";
251+
await combobox.trigger("keydown.down");
255252

256-
// Some Vuetify menu items render under .v-overlay-container without
257-
// the v-list-item class on the focused descendant; assert the ancestor
258-
// check catches them too.
259-
const overlay = document.createElement("div");
260-
overlay.className = "v-overlay-container";
261-
const focusableInside = document.createElement("div");
262-
overlay.appendChild(focusableInside);
263-
document.body.appendChild(overlay);
264-
265-
try {
266-
wrapper.vm.onInputBlur({ relatedTarget: focusableInside });
267-
expect(wrapper.emitted("update:items")).toBeFalsy();
268-
} finally {
269-
document.body.removeChild(overlay);
270-
}
271-
});
272-
273-
it("commits pending text on blur when relatedTarget is outside the dropdown menu", () => {
274-
wrapper.vm.newItemTitle = "Manually-Typed";
275-
276-
// Tabbing to a sibling field — relatedTarget has no v-list-item /
277-
// v-overlay-container ancestry. The blur should still commit.
278-
const sibling = document.createElement("button");
279-
sibling.className = "some-other-button";
253+
expect(wrapper.emitted("update:items")).toBeFalsy();
280254

281-
wrapper.vm.onInputBlur({ relatedTarget: sibling });
255+
await combobox.trigger("keydown.enter");
282256

283257
const emitted = wrapper.emitted("update:items");
284258
expect(emitted).toBeTruthy();
285-
expect(emitted[0][0]).toEqual(expect.arrayContaining([expect.objectContaining({ title: "Manually-Typed", action: "add", isNew: true })]));
259+
expect(emitted[0][0]).toEqual(expect.arrayContaining([expect.objectContaining({ title: "ca", action: "add", isNew: true })]));
286260
});
287261
});
288262

0 commit comments

Comments
 (0)