Skip to content

Commit 039c121

Browse files
committed
chore: fix onblur issue and add tab action config
1 parent 0a34c9d commit 039c121

File tree

4 files changed

+86
-33
lines changed

4 files changed

+86
-33
lines changed

packages/pluggableWidgets/rich-text-web/src/RichText.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,22 @@
216216
<enumerationValue key="characterCountHtml">Character count (including HTML)</enumerationValue>
217217
</enumerationValues>
218218
</property>
219+
<property key="tabAction" type="enumeration" defaultValue="changeFocus">
220+
<caption>Tab key behavior</caption>
221+
<description>Press 'Tab' to indent text or to move focus to the next element.</description>
222+
<enumerationValues>
223+
<enumerationValue key="indent">Indent</enumerationValue>
224+
<enumerationValue key="changeFocus">Focus next</enumerationValue>
225+
</enumerationValues>
226+
</property>
227+
<property key="ctrlTabAction" type="enumeration" defaultValue="indent">
228+
<caption>Ctrl+Tab key behavior</caption>
229+
<description>Press 'Ctrl+Tab' to indent text or to move focus to the next element.</description>
230+
<enumerationValues>
231+
<enumerationValue key="indent">Indent</enumerationValue>
232+
<enumerationValue key="changeFocus">Focus next</enumerationValue>
233+
</enumerationValues>
234+
</property>
219235
</propertyGroup>
220236
</propertyGroup>
221237
<propertyGroup caption="Custom toolbar">

packages/pluggableWidgets/rich-text-web/src/components/EditorWrapper.tsx

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import { If } from "@mendix/widget-plugin-component-kit/If";
22
import { useDebounceWithStatus } from "@mendix/widget-plugin-hooks/useDebounceWithStatus";
33
import { executeAction } from "@mendix/widget-plugin-platform/framework/execute-action";
44
import classNames from "classnames";
5-
import Quill, { Range } from "quill";
5+
import Quill from "quill";
66
import "quill/dist/quill.core.css";
77
import "quill/dist/quill.snow.css";
88
import { CSSProperties, ReactElement, useCallback, useContext, useEffect, useRef, useState } from "react";
99
import { RichTextContainerProps } from "typings/RichTextProps";
1010
import { EditorContext, EditorProvider } from "../store/EditorProvider";
11+
import { useActionEvents } from "../store/useActionEvents";
1112
import { updateLegacyQuillFormats } from "../utils/helpers";
1213
import MendixTheme from "../utils/themes/mxTheme";
1314
import { createPreset } from "./CustomToolbars/presets";
@@ -50,11 +51,9 @@ function EditorWrapperInner(props: EditorWrapperProps): ReactElement {
5051
} = props;
5152

5253
const globalState = useContext(EditorContext);
53-
5454
const isFirstLoad = useRef<boolean>(false);
5555
const quillRef = useRef<Quill>(null);
56-
const [isFocus, setIsFocus] = useState(false);
57-
const editorValueRef = useRef<string>("");
56+
const actionEvents = useActionEvents({ onBlur, onFocus, onChange, onChangeType, quill: quillRef?.current });
5857
const toolbarRef = useRef<HTMLDivElement>(null);
5958
const [wordCount, setWordCount] = useState(0);
6059

@@ -128,34 +127,6 @@ function EditorWrapperInner(props: EditorWrapperProps): ReactElement {
128127
// eslint-disable-next-line react-hooks/exhaustive-deps
129128
}, [quillRef.current, stringAttribute, calculateCounts, onChange?.isExecuting]);
130129

131-
const onSelectionChange = useCallback(
132-
(range: Range) => {
133-
if (range) {
134-
// User cursor is selecting
135-
if (!isFocus) {
136-
setIsFocus(true);
137-
executeAction(onFocus);
138-
editorValueRef.current = quillRef.current?.getText() || "";
139-
}
140-
} else {
141-
// Cursor not in the editor
142-
if (isFocus) {
143-
setIsFocus(false);
144-
executeAction(onBlur);
145-
146-
if (onChangeType === "onLeave") {
147-
if (editorValueRef.current !== quillRef.current?.getText()) {
148-
executeAction(onChange);
149-
}
150-
}
151-
}
152-
}
153-
(quillRef.current?.theme as MendixTheme).updatePicker(range);
154-
},
155-
156-
[isFocus, onFocus, onBlur, onChange, onChangeType]
157-
);
158-
159130
const toolbarId = `widget_${id.replaceAll(".", "_")}_toolbar`;
160131
const shouldHideToolbar = (stringAttribute.readOnly && readOnlyStyle !== "text") || toolbarLocation === "hide";
161132
const toolbarPreset = shouldHideToolbar ? [] : createPreset(props);
@@ -183,6 +154,7 @@ function EditorWrapperInner(props: EditorWrapperProps): ReactElement {
183154
}}
184155
spellCheck={props.spellCheck}
185156
tabIndex={tabIndex}
157+
{...actionEvents}
186158
>
187159
<If condition={toolbarLocation === "auto"}>
188160
<StickySentinel />
@@ -220,7 +192,7 @@ function EditorWrapperInner(props: EditorWrapperProps): ReactElement {
220192
}
221193
toolbarId={shouldHideToolbar ? undefined : toolbarOptions ? toolbarOptions : toolbarId}
222194
onTextChange={onTextChange}
223-
onSelectionChange={onSelectionChange}
195+
// onSelectionChange={onSelectionChange}
224196
className={"widget-rich-text-container"}
225197
readOnly={stringAttribute.readOnly}
226198
key={`${toolbarId}_${stringAttribute.readOnly}`}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { executeAction } from "@mendix/widget-plugin-platform/framework/execute-action";
2+
import Quill from "quill";
3+
import { FocusEvent, useMemo, useRef } from "react";
4+
import { RichTextContainerProps } from "typings/RichTextProps";
5+
6+
type UseActionEventsReturnValue = {
7+
onFocus: (e: FocusEvent) => void;
8+
onBlur: (e: FocusEvent) => void;
9+
};
10+
11+
interface useActionEventsProps
12+
extends Pick<RichTextContainerProps, "onFocus" | "onBlur" | "onChange" | "onChangeType"> {
13+
quill?: Quill | null;
14+
}
15+
16+
function isInternalTarget(
17+
currentTarget: EventTarget & Element,
18+
relatedTarget: (EventTarget & Element) | null
19+
): boolean | undefined {
20+
return (
21+
currentTarget?.contains(relatedTarget) ||
22+
currentTarget?.ownerDocument.querySelector(".widget-rich-text-modal-body")?.contains(relatedTarget)
23+
);
24+
}
25+
26+
export function useActionEvents(props: useActionEventsProps): UseActionEventsReturnValue {
27+
const editorValueRef = useRef<string>("");
28+
return useMemo(() => {
29+
return {
30+
onFocus: (e: FocusEvent): void => {
31+
const { relatedTarget, currentTarget } = e;
32+
if (!isInternalTarget(currentTarget, relatedTarget)) {
33+
executeAction(props.onFocus);
34+
editorValueRef.current = props.quill?.getText() || "";
35+
}
36+
},
37+
onBlur: (e: FocusEvent): void => {
38+
const { relatedTarget, currentTarget } = e;
39+
if (!isInternalTarget(currentTarget, relatedTarget)) {
40+
executeAction(props.onBlur);
41+
if (props.onChangeType === "onLeave") {
42+
if (props.quill) {
43+
// validate if the text really changed
44+
const currentText = props.quill.getText();
45+
if (currentText !== editorValueRef.current) {
46+
executeAction(props.onChange);
47+
editorValueRef.current = currentText;
48+
}
49+
} else {
50+
executeAction(props.onChange);
51+
}
52+
}
53+
}
54+
}
55+
};
56+
}, [props.onFocus, props.quill, props.onBlur, props.onChangeType, props.onChange]);
57+
}

packages/pluggableWidgets/rich-text-web/typings/RichTextProps.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export interface CustomFontsType {
3333

3434
export type StatusBarContentEnum = "wordCount" | "characterCount" | "characterCountHtml";
3535

36+
export type TabActionEnum = "indent" | "changeFocus";
37+
38+
export type CtrlTabActionEnum = "indent" | "changeFocus";
39+
3640
export type ToolbarConfigEnum = "basic" | "advanced";
3741

3842
export type CtItemTypeEnum = "separator" | "undo" | "redo" | "bold" | "italic" | "underline" | "strike" | "superScript" | "subScript" | "orderedList" | "bulletList" | "lowerAlphaList" | "checkList" | "minIndent" | "plusIndent" | "direction" | "link" | "image" | "video" | "formula" | "blockquote" | "code" | "codeBlock" | "viewCode" | "align" | "centerAlign" | "rightAlign" | "font" | "size" | "color" | "background" | "header" | "fullscreen" | "clean" | "tableBetter";
@@ -82,6 +86,8 @@ export interface RichTextContainerProps {
8286
imageSourceContent?: ReactNode;
8387
enableDefaultUpload: boolean;
8488
statusBarContent: StatusBarContentEnum;
89+
tabAction: TabActionEnum;
90+
ctrlTabAction: CtrlTabActionEnum;
8591
toolbarConfig: ToolbarConfigEnum;
8692
history: boolean;
8793
fontStyle: boolean;
@@ -131,6 +137,8 @@ export interface RichTextPreviewProps {
131137
imageSourceContent: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> };
132138
enableDefaultUpload: boolean;
133139
statusBarContent: StatusBarContentEnum;
140+
tabAction: TabActionEnum;
141+
ctrlTabAction: CtrlTabActionEnum;
134142
toolbarConfig: ToolbarConfigEnum;
135143
history: boolean;
136144
fontStyle: boolean;

0 commit comments

Comments
 (0)