Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/pluggableWidgets/rich-text-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Fixed

- We fixed an issue where the On change action ran when leaving the editor without changing its content.

## [5.1.0] - 2026-09-29

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,35 @@ describe("Rich Text runtime behaviour", () => {
expect(onChange.execute).toHaveBeenCalledTimes(1);
});

it("does not execute onChange on blur without an edit when onChangeType is onLeave", () => {
const onChange = actionValue();
const onBlur = actionValue();
const { container } = render(
<RichText {...defaultProps} onChangeType="onLeave" onChange={onChange} onBlur={onBlur} />
);
flush();

fireEvent.focus(getEditorDom(container));
fireEvent.blur(getEditorDom(container));

expect(onChange.execute).not.toHaveBeenCalled();
expect(onBlur.execute).toHaveBeenCalledTimes(1);
});

it("executes onChange on leave once per edit when onChangeType is onLeave", () => {
const onChange = actionValue();
const { container } = render(<RichText {...defaultProps} onChangeType="onLeave" onChange={onChange} />);

fireEvent.focus(getEditorDom(container));
edit(container, "<p>edited</p>");
fireEvent.blur(getEditorDom(container));
expect(onChange.execute).toHaveBeenCalledTimes(1);

fireEvent.focus(getEditorDom(container));
fireEvent.blur(getEditorDom(container));
expect(onChange.execute).toHaveBeenCalledTimes(1);
});

it("executes onChange on edit and not on blur when onChangeType is onDataChange", () => {
const onChange = actionValue();
const { container } = render(
Expand Down Expand Up @@ -176,6 +205,7 @@ describe("Rich Text runtime behaviour", () => {

rerender(<RichText {...defaultProps} onChangeType="onLeave" {...second} />);
fireEvent.focus(getEditorDom(container));
edit(container, "<p>edited</p>");
fireEvent.blur(getEditorDom(container));

expect(second.onFocus.execute).toHaveBeenCalledTimes(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ const Editor = forwardRef<EditorHandle, EditorProps>((props, ref) => {
const uploadEnabled = enableDefaultUpload || imageSource == null;
const configRef = useRef({ enableDefaultUpload: uploadEnabled, editable: !readOnly });
configRef.current = { enableDefaultUpload: uploadEnabled, editable: !readOnly };
// Set only by a genuine edit (`onUpdate`); the external-value sync below uses
// `emitUpdate: false`, so it never marks the editor dirty.
const isDirtyRef = useRef(false);

const extensions = useMemo(
() => [
Expand Down Expand Up @@ -367,6 +370,7 @@ const Editor = forwardRef<EditorHandle, EditorProps>((props, ref) => {
content: defaultValue || "",
editable: !readOnly,
onUpdate: ({ editor }) => {
isDirtyRef.current = true;
const html = editor.isEmpty ? "" : editor.getHTML();
onUpdate?.(html);
},
Expand All @@ -375,7 +379,8 @@ const Editor = forwardRef<EditorHandle, EditorProps>((props, ref) => {
},
onBlur: () => {
executeAction(actionRef.current.onBlur);
if (actionRef.current.onChangeType === "onLeave") {
if (actionRef.current.onChangeType === "onLeave" && isDirtyRef.current) {
isDirtyRef.current = false;
executeAction(actionRef.current.onChange);
}
},
Expand Down
Loading