diff --git a/packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.spec.tsx b/packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.spec.tsx
index b9bc471acdc..0a27fb6c480 100644
--- a/packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.spec.tsx
+++ b/packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.spec.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
-import { Basic } from './InPlaceEditor.stories';
+import { Basic, WithSiblingControl } from './InPlaceEditor.stories';
describe('InPlaceEditor', () => {
it('should render the field value on mount', async () => {
@@ -54,4 +54,37 @@ describe('InPlaceEditor', () => {
await screen.findByLabelText('Cancel');
});
});
+ describe('blur', () => {
+ it('should save when focus moves to a control outside the editor', async () => {
+ render();
+ const value = await screen.findByText('John Doe');
+ value.click();
+ const input = await screen.findByDisplayValue('John Doe');
+ fireEvent.change(input, { target: { value: 'Jane Doe' } });
+ const nextButton = screen.getByRole('button', { name: 'Next' });
+ fireEvent.blur(input, { relatedTarget: nextButton });
+ await screen.findByText('Jane Doe');
+ });
+ it('should cancel when focus moves to a control outside the editor and cancelOnBlur is set', async () => {
+ render();
+ const value = await screen.findByText('John Doe');
+ value.click();
+ const input = await screen.findByDisplayValue('John Doe');
+ fireEvent.change(input, { target: { value: 'Jane Doe' } });
+ const nextButton = screen.getByRole('button', { name: 'Next' });
+ fireEvent.blur(input, { relatedTarget: nextButton });
+ await screen.findByText('John Doe');
+ expect(screen.queryByDisplayValue('Jane Doe')).toBeNull();
+ });
+ it('should keep editing when focus moves to a button inside the editor', async () => {
+ render();
+ const value = await screen.findByText('John Doe');
+ value.click();
+ const input = await screen.findByDisplayValue('John Doe');
+ fireEvent.change(input, { target: { value: 'Jane Doe' } });
+ const saveButton = screen.getByLabelText('Save');
+ fireEvent.blur(input, { relatedTarget: saveButton });
+ expect(screen.getByDisplayValue('Jane Doe')).not.toBeNull();
+ });
+ });
});
diff --git a/packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.stories.tsx b/packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.stories.tsx
index 19ad352c879..ff2b420fb28 100644
--- a/packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.stories.tsx
+++ b/packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.stories.tsx
@@ -175,6 +175,21 @@ export const CancelOnBlur = () => (
);
+export const WithSiblingControl = ({
+ cancelOnBlur,
+}: {
+ cancelOnBlur?: boolean;
+}) => (
+
+
+
+
+);
+
export const MutationMode = () => (
diff --git a/packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.tsx b/packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.tsx
index d8756b61b12..4138e15c2a4 100644
--- a/packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.tsx
+++ b/packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.tsx
@@ -196,7 +196,13 @@ export const InPlaceEditor = <
};
const handleBlur = (event: React.FocusEvent) => {
- if (event.relatedTarget) {
+ // Keep editing when focus moves to another element inside the editor
+ // (e.g. the Save and Cancel buttons). Only save or cancel when focus
+ // leaves the editor entirely.
+ if (
+ event.relatedTarget &&
+ event.currentTarget.contains(event.relatedTarget as Node)
+ ) {
return;
}
if (cancelOnBlur) {