diff --git a/packages/pluggableWidgets/rich-text-web/CHANGELOG.md b/packages/pluggableWidgets/rich-text-web/CHANGELOG.md
index 9577233fc9..516af6ecfa 100644
--- a/packages/pluggableWidgets/rich-text-web/CHANGELOG.md
+++ b/packages/pluggableWidgets/rich-text-web/CHANGELOG.md
@@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]
+### Fixed
+
+- We fixed an issue where onblur and onchange when user leave editor events not firing correctly if a focusable element is clicked as change focus user action.
+
+### Changed
+
+- We changed Tab keyboard behavior to add indentation instead of exiting focus from editor.
+- We changed ` ` mark for empty line in favor for `
` break tag instead.
+
+### Added
+
+- We added alt+F11 keyboard shortcut to do focus next, and alt+F10 to focus on toolbar.
+- We added shift+enter keyboard shortcut to add `
` break tag.
+
## [4.10.0] - 2025-10-02
### Fixed
diff --git a/packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js-snapshots/readOnlyModeBasic-chromium-linux.png b/packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js-snapshots/readOnlyModeBasic-chromium-linux.png
index 853e9dfa08..7ab375569b 100644
Binary files a/packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js-snapshots/readOnlyModeBasic-chromium-linux.png and b/packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js-snapshots/readOnlyModeBasic-chromium-linux.png differ
diff --git a/packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js-snapshots/readOnlyModeBordered-chromium-linux.png b/packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js-snapshots/readOnlyModeBordered-chromium-linux.png
index d10c284759..13fc0d652d 100644
Binary files a/packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js-snapshots/readOnlyModeBordered-chromium-linux.png and b/packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js-snapshots/readOnlyModeBordered-chromium-linux.png differ
diff --git a/packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js-snapshots/readOnlyModeReadPanel-chromium-linux.png b/packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js-snapshots/readOnlyModeReadPanel-chromium-linux.png
index 90755b6f6a..f286c64de9 100644
Binary files a/packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js-snapshots/readOnlyModeReadPanel-chromium-linux.png and b/packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js-snapshots/readOnlyModeReadPanel-chromium-linux.png differ
diff --git a/packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js-snapshots/viewCodeDialog-chromium-linux.png b/packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js-snapshots/viewCodeDialog-chromium-linux.png
index fc969c934f..ad34390f9c 100644
Binary files a/packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js-snapshots/viewCodeDialog-chromium-linux.png and b/packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js-snapshots/viewCodeDialog-chromium-linux.png differ
diff --git a/packages/pluggableWidgets/rich-text-web/package.json b/packages/pluggableWidgets/rich-text-web/package.json
index 9867313914..fffecb02c9 100644
--- a/packages/pluggableWidgets/rich-text-web/package.json
+++ b/packages/pluggableWidgets/rich-text-web/package.json
@@ -1,7 +1,7 @@
{
"name": "@mendix/rich-text-web",
"widgetName": "RichText",
- "version": "4.10.0",
+ "version": "4.11.0",
"description": "Rich inline or toolbar text editing",
"copyright": "© Mendix Technology BV 2025. All rights reserved.",
"license": "Apache-2.0",
diff --git a/packages/pluggableWidgets/rich-text-web/src/__tests__/__snapshots__/RichText.spec.tsx.snap b/packages/pluggableWidgets/rich-text-web/src/__tests__/__snapshots__/RichText.spec.tsx.snap
index d7096f79bc..3b208a808c 100644
--- a/packages/pluggableWidgets/rich-text-web/src/__tests__/__snapshots__/RichText.spec.tsx.snap
+++ b/packages/pluggableWidgets/rich-text-web/src/__tests__/__snapshots__/RichText.spec.tsx.snap
@@ -4228,6 +4228,10 @@ exports[`Rich Text renders richtext widget with readonly config 1`] = `
+
`;
@@ -4818,7 +4822,7 @@ exports[`Rich Text renders with HTML character count status bar 1`] = `
>
- 77
+ 62
character
diff --git a/packages/pluggableWidgets/rich-text-web/src/components/Editor.tsx b/packages/pluggableWidgets/rich-text-web/src/components/Editor.tsx
index 17b0ce936f..c476e1a793 100644
--- a/packages/pluggableWidgets/rich-text-web/src/components/Editor.tsx
+++ b/packages/pluggableWidgets/rich-text-web/src/components/Editor.tsx
@@ -16,20 +16,14 @@ import { SET_FULLSCREEN_ACTION } from "../store/store";
import "../utils/customPluginRegisters";
import { FontStyleAttributor, formatCustomFonts } from "../utils/formats/fonts";
import "../utils/formats/quill-table-better/assets/css/quill-table-better.scss";
-import QuillTableBetter from "../utils/formats/quill-table-better/quill-table-better";
-import { RESIZE_MODULE_CONFIG } from "../utils/formats/resizeModuleConfig";
+import { getResizeModuleConfig } from "../utils/formats/resizeModuleConfig";
import { ACTION_DISPATCHER } from "../utils/helpers";
+import { getKeyboardBindings } from "../utils/modules/keyboard";
+import { getIndentHandler } from "../utils/modules/toolbarHandlers";
+import MxUploader from "../utils/modules/uploader";
import MxQuill from "../utils/MxQuill";
-import {
- enterKeyKeyboardHandler,
- exitFullscreenKeyboardHandler,
- getIndentHandler,
- gotoStatusBarKeyboardHandler,
- gotoToolbarKeyboardHandler
-} from "./CustomToolbars/toolbarHandlers";
import { useEmbedModal } from "./CustomToolbars/useEmbedModal";
import Dialog from "./ModalDialog/Dialog";
-import MxUploader from "../utils/modules/uploader";
export interface EditorProps
extends Pick {
@@ -115,26 +109,7 @@ const Editor = forwardRef((props: EditorProps, ref: MutableRefObject(false);
const quillRef = useRef(null);
- const [isFocus, setIsFocus] = useState(false);
- const editorValueRef = useRef("");
+ const actionEvents = useActionEvents({ onBlur, onFocus, onChange, onChangeType, quill: quillRef?.current });
const toolbarRef = useRef(null);
const [wordCount, setWordCount] = useState(0);
@@ -128,34 +127,6 @@ function EditorWrapperInner(props: EditorWrapperProps): ReactElement {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [quillRef.current, stringAttribute, calculateCounts, onChange?.isExecuting]);
- const onSelectionChange = useCallback(
- (range: Range) => {
- if (range) {
- // User cursor is selecting
- if (!isFocus) {
- setIsFocus(true);
- executeAction(onFocus);
- editorValueRef.current = quillRef.current?.getText() || "";
- }
- } else {
- // Cursor not in the editor
- if (isFocus) {
- setIsFocus(false);
- executeAction(onBlur);
-
- if (onChangeType === "onLeave") {
- if (editorValueRef.current !== quillRef.current?.getText()) {
- executeAction(onChange);
- }
- }
- }
- }
- (quillRef.current?.theme as MendixTheme).updatePicker(range);
- },
-
- [isFocus, onFocus, onBlur, onChange, onChangeType]
- );
-
const toolbarId = `widget_${id.replaceAll(".", "_")}_toolbar`;
const shouldHideToolbar = (stringAttribute.readOnly && readOnlyStyle !== "text") || toolbarLocation === "hide";
const toolbarPreset = shouldHideToolbar ? [] : createPreset(props);
@@ -182,7 +153,8 @@ function EditorWrapperInner(props: EditorWrapperProps): ReactElement {
}
}}
spellCheck={props.spellCheck}
- tabIndex={tabIndex}
+ tabIndex={tabIndex ?? -1}
+ {...actionEvents}
>
@@ -220,7 +192,6 @@ function EditorWrapperInner(props: EditorWrapperProps): ReactElement {
}
toolbarId={shouldHideToolbar ? undefined : toolbarOptions ? toolbarOptions : toolbarId}
onTextChange={onTextChange}
- onSelectionChange={onSelectionChange}
className={"widget-rich-text-container"}
readOnly={stringAttribute.readOnly}
key={`${toolbarId}_${stringAttribute.readOnly}`}
@@ -231,15 +202,19 @@ function EditorWrapperInner(props: EditorWrapperProps): ReactElement {
formOrientation={formOrientation}
/>
-
-
+
+
+
{wordCount}
{` ${statusBarContent === "wordCount" ? "word" : "character"}`}
{wordCount === 1 ? "" : "s"}
-
-
+
+
);
}
diff --git a/packages/pluggableWidgets/rich-text-web/src/package.xml b/packages/pluggableWidgets/rich-text-web/src/package.xml
index ac7ba9fc2b..c61933a443 100644
--- a/packages/pluggableWidgets/rich-text-web/src/package.xml
+++ b/packages/pluggableWidgets/rich-text-web/src/package.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/packages/pluggableWidgets/rich-text-web/src/store/useActionEvents.ts b/packages/pluggableWidgets/rich-text-web/src/store/useActionEvents.ts
new file mode 100644
index 0000000000..67315da04a
--- /dev/null
+++ b/packages/pluggableWidgets/rich-text-web/src/store/useActionEvents.ts
@@ -0,0 +1,57 @@
+import { executeAction } from "@mendix/widget-plugin-platform/framework/execute-action";
+import Quill from "quill";
+import { FocusEvent, useMemo, useRef } from "react";
+import { RichTextContainerProps } from "typings/RichTextProps";
+
+type UseActionEventsReturnValue = {
+ onFocus: (e: FocusEvent) => void;
+ onBlur: (e: FocusEvent) => void;
+};
+
+interface useActionEventsProps
+ extends Pick {
+ quill?: Quill | null;
+}
+
+function isInternalTarget(
+ currentTarget: EventTarget & Element,
+ relatedTarget: (EventTarget & Element) | null
+): boolean | undefined {
+ return (
+ currentTarget?.contains(relatedTarget) ||
+ currentTarget?.ownerDocument.querySelector(".widget-rich-text-modal-body")?.contains(relatedTarget)
+ );
+}
+
+export function useActionEvents(props: useActionEventsProps): UseActionEventsReturnValue {
+ const editorValueRef = useRef("");
+ return useMemo(() => {
+ return {
+ onFocus: (e: FocusEvent): void => {
+ const { relatedTarget, currentTarget } = e;
+ if (!isInternalTarget(currentTarget, relatedTarget)) {
+ executeAction(props.onFocus);
+ editorValueRef.current = props.quill?.getText() || "";
+ }
+ },
+ onBlur: (e: FocusEvent): void => {
+ const { relatedTarget, currentTarget } = e;
+ if (!isInternalTarget(currentTarget, relatedTarget)) {
+ executeAction(props.onBlur);
+ if (props.onChangeType === "onLeave") {
+ if (props.quill) {
+ // validate if the text really changed
+ const currentText = props.quill.getText();
+ if (currentText !== editorValueRef.current) {
+ executeAction(props.onChange);
+ editorValueRef.current = currentText;
+ }
+ } else {
+ executeAction(props.onChange);
+ }
+ }
+ }
+ }
+ };
+ }, [props.onFocus, props.quill, props.onBlur, props.onChangeType, props.onChange]);
+}
diff --git a/packages/pluggableWidgets/rich-text-web/src/ui/RichText.scss b/packages/pluggableWidgets/rich-text-web/src/ui/RichText.scss
index dbd4738154..36103a40c4 100644
--- a/packages/pluggableWidgets/rich-text-web/src/ui/RichText.scss
+++ b/packages/pluggableWidgets/rich-text-web/src/ui/RichText.scss
@@ -88,9 +88,8 @@ $rte-brand-primary: #264ae5;
display: none;
}
- .widget-rich-text-footer {
+ &-footer {
align-items: center;
- border-top: 1px solid var(--border-color-default, $rte-border-color-default);
color: var(--font-color-detail);
display: flex;
flex: 0 0 auto;
@@ -101,6 +100,14 @@ $rte-brand-primary: #264ae5;
position: relative;
text-transform: none;
justify-content: end;
+
+ &:not(.hide-status-bar) {
+ border-top: 1px solid var(--border-color-default, $rte-border-color-default);
+ }
+
+ &:focus-within {
+ border-top: 1px solid var(--form-input-border-focus-color, var(--brand-primary, $rte-brand-primary));
+ }
}
&.editor-readPanel {
diff --git a/packages/pluggableWidgets/rich-text-web/src/utils/MxQuill.ts b/packages/pluggableWidgets/rich-text-web/src/utils/MxQuill.ts
index 6a637f0517..cdff902b9d 100644
--- a/packages/pluggableWidgets/rich-text-web/src/utils/MxQuill.ts
+++ b/packages/pluggableWidgets/rich-text-web/src/utils/MxQuill.ts
@@ -174,7 +174,7 @@ function convertHTML(blot: Blot, index: number, length: number, isRoot = false):
}
if (blot instanceof TextBlot) {
const escapedText = escapeText(blot.value().slice(index, index + length));
- return escapedText.replaceAll(" ", " ");
+ return escapedText;
}
if (blot instanceof ParentBlot) {
// TODO fix API
diff --git a/packages/pluggableWidgets/rich-text-web/src/utils/customPluginRegisters.ts b/packages/pluggableWidgets/rich-text-web/src/utils/customPluginRegisters.ts
index df383fcf3c..3f10685fc0 100644
--- a/packages/pluggableWidgets/rich-text-web/src/utils/customPluginRegisters.ts
+++ b/packages/pluggableWidgets/rich-text-web/src/utils/customPluginRegisters.ts
@@ -6,6 +6,7 @@ import CustomListItem from "./formats/customList";
import CustomLink from "./formats/link";
import CustomVideo from "./formats/video";
import CustomImage from "./formats/image";
+import SoftBreak from "./formats/softBreak";
import Button from "./formats/button";
import { Attributor } from "parchment";
const direction = Quill.import("attributors/style/direction") as Attributor;
@@ -18,6 +19,7 @@ import MxUploader from "./modules/uploader";
import MxBlock from "./formats/block";
import CustomClipboard from "./modules/clipboard";
import { WhiteSpaceStyle } from "./formats/whiteSpace";
+
class Empty {
doSomething(): string {
return "";
@@ -33,6 +35,7 @@ Quill.register(WhiteSpaceStyle, true);
Quill.register(CustomLink, true);
Quill.register(CustomVideo, true);
Quill.register(CustomImage, true);
+Quill.register({ "formats/softbreak": SoftBreak }, true);
Quill.register(direction, true);
Quill.register(alignment, true);
Quill.register(IndentLeftStyle, true);
diff --git a/packages/pluggableWidgets/rich-text-web/src/utils/formats/block.ts b/packages/pluggableWidgets/rich-text-web/src/utils/formats/block.ts
index 2a7460d457..802cc43001 100644
--- a/packages/pluggableWidgets/rich-text-web/src/utils/formats/block.ts
+++ b/packages/pluggableWidgets/rich-text-web/src/utils/formats/block.ts
@@ -5,9 +5,9 @@ class MxBlock extends Block {
// quill return empty paragraph when there is no content (just empty line)
// to preserve the line breaks, we add empty space
if (this.domNode.childElementCount === 1 && this.domNode.children[0] instanceof HTMLBRElement) {
- return this.domNode.outerHTML.replace(/
/g, " ");
+ return this.domNode.outerHTML.replace(/
/g, "
");
} else if (this.domNode.childElementCount === 0 && this.domNode.textContent?.trim() === "") {
- this.domNode.innerHTML = " ";
+ this.domNode.innerHTML = "
";
return this.domNode.outerHTML;
} else {
return this.domNode.outerHTML;
diff --git a/packages/pluggableWidgets/rich-text-web/src/utils/formats/resizeModuleConfig.ts b/packages/pluggableWidgets/rich-text-web/src/utils/formats/resizeModuleConfig.ts
index 72f64d6dfb..c6a8bf24f7 100644
--- a/packages/pluggableWidgets/rich-text-web/src/utils/formats/resizeModuleConfig.ts
+++ b/packages/pluggableWidgets/rich-text-web/src/utils/formats/resizeModuleConfig.ts
@@ -62,3 +62,10 @@ export const RESIZE_MODULE_CONFIG = {
}
}
};
+
+export function getResizeModuleConfig(isReadOnly?: boolean): Record | undefined {
+ if (isReadOnly) {
+ return {};
+ }
+ return { resize: RESIZE_MODULE_CONFIG };
+}
diff --git a/packages/pluggableWidgets/rich-text-web/src/utils/formats/softBreak.ts b/packages/pluggableWidgets/rich-text-web/src/utils/formats/softBreak.ts
new file mode 100644
index 0000000000..c37942ef43
--- /dev/null
+++ b/packages/pluggableWidgets/rich-text-web/src/utils/formats/softBreak.ts
@@ -0,0 +1,17 @@
+// import { BlockEmbed } from "quill/blots/block";
+import { EmbedBlot } from "parchment";
+/**
+ * custom video link handler, allowing width and height config
+ */
+class SoftBreak extends EmbedBlot {
+ static create(_value: unknown): Element {
+ const node = super.create() as HTMLElement;
+ return node;
+ }
+}
+
+// SoftBreak.scope = Scope.INLINE_BLOT;
+SoftBreak.blotName = "softbreak";
+SoftBreak.tagName = "BR";
+
+export default SoftBreak;
diff --git a/packages/pluggableWidgets/rich-text-web/src/utils/modules/keyboard.ts b/packages/pluggableWidgets/rich-text-web/src/utils/modules/keyboard.ts
new file mode 100644
index 0000000000..cb5706e02d
--- /dev/null
+++ b/packages/pluggableWidgets/rich-text-web/src/utils/modules/keyboard.ts
@@ -0,0 +1,67 @@
+import {
+ addIndentText,
+ enterKeyKeyboardHandler,
+ exitFullscreenKeyboardHandler,
+ gotoStatusBarKeyboardHandler,
+ gotoToolbarKeyboardHandler,
+ moveIndent,
+ moveOutdent,
+ movePrevFocus,
+ shiftEnterKeyKeyboardHandler
+} from "./toolbarHandlers";
+import QuillTableBetter from "../formats/quill-table-better/quill-table-better";
+
+export function getKeyboardBindings(): Record {
+ const defaultBindings: Record = {
+ enter: {
+ key: "Enter",
+ handler: enterKeyKeyboardHandler
+ },
+ shiftEnter: {
+ key: "Enter",
+ shiftKey: true,
+ collapsed: true,
+ handler: shiftEnterKeyKeyboardHandler
+ },
+ focusTab: {
+ key: "F10",
+ altKey: true,
+ collapsed: true,
+ handler: gotoToolbarKeyboardHandler
+ },
+ shiftTab: {
+ key: "Tab",
+ shiftKey: true,
+ handler: movePrevFocus
+ },
+ outdent: {
+ key: "Tab",
+ shiftKey: true,
+ format: ["blockquote", "indent", "list"],
+ // highlight tab or tab at beginning of list, indent or blockquote
+ handler: moveOutdent
+ },
+ indent: {
+ // highlight tab or tab at beginning of list, indent or blockquote
+ key: "Tab",
+ format: ["blockquote", "indent", "list"],
+ handler: moveIndent
+ },
+ nextFocusTab: {
+ key: "F11",
+ altKey: true,
+ collapsed: true,
+ handler: gotoStatusBarKeyboardHandler
+ },
+ escape: {
+ key: "Escape",
+ handler: exitFullscreenKeyboardHandler
+ },
+ tab: {
+ key: "Tab",
+ handler: addIndentText
+ },
+ ...QuillTableBetter.keyboardBindings
+ };
+ return defaultBindings;
+}
diff --git a/packages/pluggableWidgets/rich-text-web/src/components/CustomToolbars/toolbarHandlers.ts b/packages/pluggableWidgets/rich-text-web/src/utils/modules/toolbarHandlers.ts
similarity index 52%
rename from packages/pluggableWidgets/rich-text-web/src/components/CustomToolbars/toolbarHandlers.ts
rename to packages/pluggableWidgets/rich-text-web/src/utils/modules/toolbarHandlers.ts
index beb309c033..d8fdf8dcad 100644
--- a/packages/pluggableWidgets/rich-text-web/src/components/CustomToolbars/toolbarHandlers.ts
+++ b/packages/pluggableWidgets/rich-text-web/src/utils/modules/toolbarHandlers.ts
@@ -4,9 +4,15 @@ import { MutableRefObject } from "react";
import { Range } from "quill/core/selection";
import Keyboard, { Context } from "quill/modules/keyboard";
import { Scope } from "parchment";
-import { ACTION_DISPATCHER } from "../../utils/helpers";
+import { ACTION_DISPATCHER } from "../helpers";
import { SET_FULLSCREEN_ACTION } from "../../store/store";
+function returnWithStopPropagation(context: Context): boolean {
+ context.event.stopPropagation();
+ context.event.preventDefault();
+ return true;
+}
+
/**
* give custom indent handler to use our custom "indent-left" and "indent-right" formats (formats/indent.ts)
*/
@@ -70,13 +76,64 @@ export function enterKeyKeyboardHandler(this: Keyboard, range: Range, context: C
});
}
+export function shiftEnterKeyKeyboardHandler(this: Keyboard, range: Range, context: Context): any {
+ if (context.format.table) {
+ return true;
+ }
+ this.quill.insertEmbed(range.index, "softbreak", true, Quill.sources.USER);
+ this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
+ return false;
+}
+
+export function movePrevFocus(this: Keyboard, range: Range, context: Context): any {
+ if (context.format.table) {
+ return returnWithStopPropagation(context);
+ } else if (context.collapsed) {
+ if (context.format.indent || context.format.list || context.format.blockquote) {
+ return returnWithStopPropagation(context);
+ }
+ }
+
+ gotoToolbarKeyboardHandler.call(this, range, context);
+ return returnWithStopPropagation(context);
+}
+
+// Copied from https://github.com/slab/quill/blob/539cbffd0a13b18e9c65eb84dd35e6596e403158/packages/quill/src/modules/keyboard.ts#L372
+// with added stopPropagation and preventDefault
+export function moveOutdent(this: Keyboard, _range: Range, context: Context): any {
+ if (context.collapsed && context.offset !== 0) {
+ return returnWithStopPropagation(context);
+ }
+ this.quill.format("indent", "-1", Quill.sources.USER);
+ return !returnWithStopPropagation(context);
+}
+
+// Copied from https://github.com/slab/quill/blob/539cbffd0a13b18e9c65eb84dd35e6596e403158/packages/quill/src/modules/keyboard.ts#L372
+// with added stopPropagation and preventDefault
+export function moveIndent(this: Keyboard, _range: Range, context: Context): any {
+ if (context.collapsed && context.offset !== 0) {
+ return returnWithStopPropagation(context);
+ }
+ this.quill.format("indent", "+1", Quill.sources.USER);
+ return !returnWithStopPropagation(context);
+}
+
// focus to first toolbar button
-export function gotoToolbarKeyboardHandler(this: Keyboard, _range: Range, _context: Context): void {
+export function gotoToolbarKeyboardHandler(this: Keyboard, _range: Range, context: Context): any {
+ if (context.format.table) {
+ return true;
+ }
+
const toolbar = this.quill.container.parentElement?.parentElement?.querySelector(".widget-rich-text-toolbar");
- (toolbar?.querySelector(".ql-formats button") as HTMLElement)?.focus();
+ if (toolbar) {
+ (toolbar?.querySelector(".ql-formats button") as HTMLElement)?.focus();
+ } else {
+ // "widget-rich-text form-control"
+ this.quill.container.parentElement?.parentElement?.parentElement?.focus();
+ }
}
-// focus to status bar button (exit editor)
+// move to next element focus : status bar button (exit editor)
export function gotoStatusBarKeyboardHandler(this: Keyboard, _range: Range, context: Context): boolean | void {
if (context.format.table) {
return true;
@@ -86,7 +143,27 @@ export function gotoStatusBarKeyboardHandler(this: Keyboard, _range: Range, cont
if (statusBar) {
(statusBar as HTMLElement)?.focus();
} else {
- this.quill.blur();
+ // "widget-rich-text form-control"
+ this.quill.container.parentElement?.parentElement?.parentElement?.focus();
+ }
+}
+
+// default quill tab handler
+// https://github.com/slab/quill/blob/539cbffd0a13b18e9c65eb84dd35e6596e403158/packages/quill/src/modules/keyboard.ts#L412
+// but modified to add stopPropagation and preventDefault
+export function addIndentText(this: Keyboard, range: Range, context: Context): boolean | void {
+ if (context.format.table) {
+ return true;
+ }
+ if (context.collapsed && context.offset === 0) {
+ return moveIndent.call(this, range, context);
+ } else {
+ this.quill.history.cutoff();
+ const delta = new Delta().retain(range.index).delete(range.length).insert("\t");
+ this.quill.updateContents(delta, Quill.sources.USER);
+ this.quill.history.cutoff();
+ this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
+ return returnWithStopPropagation(context);
}
}