feat(KB-234): add double-click edit and Escape key handling to SpecEditor
- Add double-click handler to SpecEditor for quick edit mode entry - Implement Escape key handler to exit edit mode - Add comprehensive double-click interaction tests - Add CSS styles for improved editor UX
This commit is contained in:
@@ -47,7 +47,7 @@ export function SpecEditor({
|
||||
setFeedback("");
|
||||
}, [onRequestRevision, feedback, isRequesting]);
|
||||
|
||||
// Keyboard shortcut: Ctrl/Cmd+Enter to save in edit mode
|
||||
// Keyboard shortcut: Ctrl/Cmd+Enter to save in edit mode, Escape to cancel
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (isEditing && (e.metaKey || e.ctrlKey) && e.key === "Enter") {
|
||||
@@ -55,6 +55,9 @@ export function SpecEditor({
|
||||
if (canSave) {
|
||||
void handleSave();
|
||||
}
|
||||
} else if (isEditing && e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
handleCancelEdit();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -72,6 +75,13 @@ export function SpecEditor({
|
||||
setEditContent(content);
|
||||
};
|
||||
|
||||
const handleDoubleClick = useCallback((e: React.MouseEvent) => {
|
||||
if (!readOnly && !isEditing) {
|
||||
e.stopPropagation();
|
||||
handleEnterEditMode();
|
||||
}
|
||||
}, [readOnly, isEditing, content]);
|
||||
|
||||
const stripLeadingHeading = (text: string): string => {
|
||||
return text.replace(/^#\s+[^\n]*\n+/, "");
|
||||
};
|
||||
@@ -130,7 +140,7 @@ export function SpecEditor({
|
||||
placeholder="Enter task specification in Markdown..."
|
||||
/>
|
||||
) : content ? (
|
||||
<div className="markdown-body">
|
||||
<div className="markdown-body" onDoubleClick={handleDoubleClick}>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||
{stripLeadingHeading(content)}
|
||||
</ReactMarkdown>
|
||||
|
||||
@@ -85,6 +85,63 @@ Test mission description.
|
||||
expect(screen.getByText("Test mission description.")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("enters edit mode on double-click when not in readOnly mode", () => {
|
||||
render(<SpecEditor content={mockContent} onSave={mockOnSave} />);
|
||||
|
||||
// Double-click on the markdown body
|
||||
const markdownBody = document.querySelector(".markdown-body");
|
||||
expect(markdownBody).toBeTruthy();
|
||||
fireEvent.doubleClick(markdownBody!);
|
||||
|
||||
// Should now be in edit mode
|
||||
expect(screen.getByRole("textbox")).toBeTruthy();
|
||||
const editButton = screen.getByText("Edit") as HTMLButtonElement;
|
||||
expect(editButton.classList.contains("btn-primary")).toBe(true);
|
||||
});
|
||||
|
||||
it("does not enter edit mode on double-click when readOnly is true", () => {
|
||||
render(<SpecEditor content={mockContent} readOnly={true} />);
|
||||
|
||||
// Double-click on the markdown body
|
||||
const markdownBody = document.querySelector(".markdown-body");
|
||||
expect(markdownBody).toBeTruthy();
|
||||
fireEvent.doubleClick(markdownBody!);
|
||||
|
||||
// Should NOT be in edit mode (no textarea)
|
||||
expect(screen.queryByRole("textbox")).toBeNull();
|
||||
});
|
||||
|
||||
it("does not enter edit mode on double-click when already in edit mode", () => {
|
||||
render(<SpecEditor content={mockContent} onSave={mockOnSave} />);
|
||||
|
||||
// Enter edit mode via Edit button
|
||||
fireEvent.click(screen.getByText("Edit"));
|
||||
expect(screen.getByRole("textbox")).toBeTruthy();
|
||||
|
||||
// Double-clicking again should not break anything
|
||||
const markdownBody = document.querySelector(".markdown-body");
|
||||
// Note: in edit mode, markdown-body is not rendered, so this query returns null
|
||||
expect(markdownBody).toBeNull();
|
||||
});
|
||||
|
||||
it("cancels edit mode when pressing Escape key", () => {
|
||||
render(<SpecEditor content={mockContent} onSave={mockOnSave} />);
|
||||
|
||||
// Enter edit mode
|
||||
fireEvent.click(screen.getByText("Edit"));
|
||||
const textarea = screen.getByRole("textbox") as HTMLTextAreaElement;
|
||||
|
||||
// Make a change
|
||||
fireEvent.change(textarea, { target: { value: "Changed content" } });
|
||||
|
||||
// Press Escape
|
||||
fireEvent.keyDown(textarea, { key: "Escape" });
|
||||
|
||||
// Should be back in view mode
|
||||
expect(screen.queryByRole("textbox")).toBeNull();
|
||||
expect(screen.getByText("Test mission description.")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("disables Edit button when already in edit mode", () => {
|
||||
render(<SpecEditor content={mockContent} onSave={mockOnSave} />);
|
||||
|
||||
|
||||
@@ -7857,6 +7857,11 @@ html .column.drag-over * {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Double-click to edit cursor - only when not readOnly */
|
||||
.spec-editor:not(.spec-editor-readonly) .markdown-body {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Toolbar - fixed at top */
|
||||
.spec-editor-toolbar {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user