fix(KB-183): prevent dep-dropdown clicks from dismissing InlineCreateCard
- Add onMouseDown preventDefault on dep-dropdown and items to retain focus - Extend focusout guard to check dependencies and showDeps state before cancelling - Add useEffect dependency array entries for dependencies and showDeps - Add tests for mouseDown preventDefault and blur-with-deps retention
This commit is contained in:
@@ -43,14 +43,14 @@ export function InlineCreateCard({ tasks, onSubmit, onCancel, addToast }: Inline
|
||||
const handleFocusOut = (e: FocusEvent) => {
|
||||
// relatedTarget is the element receiving focus — if it's inside the card, ignore
|
||||
if (e.relatedTarget instanceof Node && card.contains(e.relatedTarget)) return;
|
||||
// Only cancel if empty
|
||||
if (description.trim() === "" && pendingImages.length === 0) {
|
||||
// Only cancel if empty and dropdown is not open
|
||||
if (description.trim() === "" && pendingImages.length === 0 && dependencies.length === 0 && !showDeps) {
|
||||
onCancel();
|
||||
}
|
||||
};
|
||||
card.addEventListener("focusout", handleFocusOut);
|
||||
return () => card.removeEventListener("focusout", handleFocusOut);
|
||||
}, [description, pendingImages, onCancel]);
|
||||
}, [description, pendingImages, dependencies, showDeps, onCancel]);
|
||||
|
||||
// Clean up object URLs on unmount to prevent memory leaks
|
||||
useEffect(() => {
|
||||
@@ -205,7 +205,7 @@ export function InlineCreateCard({ tasks, onSubmit, onCancel, addToast }: Inline
|
||||
)
|
||||
: tasks;
|
||||
return (
|
||||
<div className="dep-dropdown">
|
||||
<div className="dep-dropdown" onMouseDown={(e) => e.preventDefault()}>
|
||||
<input
|
||||
className="dep-dropdown-search"
|
||||
placeholder="Search tasks…"
|
||||
@@ -221,6 +221,7 @@ export function InlineCreateCard({ tasks, onSubmit, onCancel, addToast }: Inline
|
||||
<div
|
||||
key={t.id}
|
||||
className={`dep-dropdown-item${dependencies.includes(t.id) ? " selected" : ""}`}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={() => toggleDep(t.id)}
|
||||
>
|
||||
<span className="dep-dropdown-id">{t.id}</span>
|
||||
|
||||
@@ -67,6 +67,43 @@ describe("InlineCreateCard blur-to-cancel", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("InlineCreateCard dep-dropdown focus retention", () => {
|
||||
const testTasks: Task[] = [
|
||||
{ id: "KB-010", title: "Task A", description: "First task", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z" },
|
||||
];
|
||||
|
||||
it("dep-dropdown-item mouseDown calls preventDefault to retain focus", () => {
|
||||
renderCard(testTasks);
|
||||
// Open the dropdown
|
||||
fireEvent.click(screen.getByText(/Deps/));
|
||||
const item = document.querySelector(".dep-dropdown-item") as HTMLElement;
|
||||
expect(item).toBeTruthy();
|
||||
|
||||
// Fire mouseDown and verify preventDefault was called —
|
||||
// this is the mechanism that keeps focus on the search input in
|
||||
// real browsers and prevents a focusout with relatedTarget: null
|
||||
const prevented = !fireEvent.mouseDown(item);
|
||||
expect(prevented).toBe(true);
|
||||
});
|
||||
|
||||
it("does NOT call onCancel when focus leaves card with selected dependencies but empty description", () => {
|
||||
const { props } = renderCard(testTasks);
|
||||
const textarea = screen.getByPlaceholderText("What needs to be done?");
|
||||
|
||||
// Open dropdown and select a dependency
|
||||
fireEvent.click(screen.getByText(/Deps/));
|
||||
const item = document.querySelector(".dep-dropdown-item") as HTMLElement;
|
||||
expect(item).toBeTruthy();
|
||||
fireEvent.click(item);
|
||||
|
||||
// Focus the textarea then blur out of the card entirely
|
||||
textarea.focus();
|
||||
fireEvent.focusOut(textarea, { relatedTarget: null });
|
||||
|
||||
expect(props.onCancel).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("InlineCreateCard dependency dropdown search", () => {
|
||||
const testTasks: Task[] = [
|
||||
{ id: "KB-001", title: "Fix login", description: "Login page broken", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z" },
|
||||
|
||||
Reference in New Issue
Block a user