carbon-react 153.8.0 → 154.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,7 @@ import { markdownNodes, theme } from "./constants";
14
14
  const SerializeLexical = editor => {
15
15
  let htmlString;
16
16
  let json;
17
- editor.update(() => {
17
+ editor.read(() => {
18
18
  // Get the current editor state
19
19
  const editorState = editor.getEditorState();
20
20
  // Serialize the editor state to JSON
@@ -0,0 +1,5 @@
1
+ import { LexicalEditor } from "lexical";
2
+ declare const EditorRefPlugin: ({ setEditorRef, }: {
3
+ setEditorRef: (editor: LexicalEditor) => void;
4
+ }) => null;
5
+ export default EditorRefPlugin;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * This plugin allows retrieval of a reference to the current editor. It's useful
3
+ * for testing purposes, where tests might need to directly interact with the editor to
4
+ * emulate e.g. blurring.
5
+ */
6
+ import { useEffect } from "react";
7
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
8
+ const EditorRefPlugin = ({
9
+ setEditorRef
10
+ }) => {
11
+ const [editor] = useLexicalComposerContext();
12
+ useEffect(() => {
13
+ setEditorRef(editor);
14
+ }, [editor, setEditorRef]);
15
+ return null;
16
+ };
17
+ export default EditorRefPlugin;
@@ -0,0 +1 @@
1
+ export { default } from "./editor-ref.plugin";
@@ -0,0 +1 @@
1
+ export { default } from "./editor-ref.plugin";
@@ -85,7 +85,6 @@ const Toolbar = ({
85
85
  "aria-label": locale.textEditor.toolbarAriaLabel(),
86
86
  "data-role": `${namespace}-toolbar`,
87
87
  id: `${namespace}-toolbar`,
88
- onFocus: e => e.stopPropagation(),
89
88
  ref: toolbarRef
90
89
  }, /*#__PURE__*/React.createElement(FormattingButtons, {
91
90
  "data-role": `${namespace}-formatting-buttons`
@@ -23,10 +23,14 @@ export interface TextEditorProps extends MarginProps, TagProps {
23
23
  labelText: string;
24
24
  /** The identifier for the Text Editor. This allows for the using of multiple Text Editors on a screen */
25
25
  namespace?: string;
26
+ /** The callback to fire when the editor loses focus */
27
+ onBlur?: (ev: React.FocusEvent<HTMLElement>) => void;
26
28
  /** The callback to fire when the Cancel button within the editor is pressed */
27
29
  onCancel?: () => void;
28
30
  /** The callback to fire when a change is registered within the editor */
29
31
  onChange?: (value: string, formattedValues: EditorFormattedValues) => void;
32
+ /** The callback to fire when the editor gains focus */
33
+ onFocus?: (ev: React.FocusEvent<HTMLElement>) => void;
30
34
  /** The callback to fire when a link is added into the editor */
31
35
  onLinkAdded?: (link: string, state: string) => void;
32
36
  /** The callback to fire when the Save button within the editor is pressed */
@@ -46,5 +50,5 @@ export interface TextEditorProps extends MarginProps, TagProps {
46
50
  /** The initial value of the editor, as a HTML string, or JSON */
47
51
  value?: string | undefined;
48
52
  }
49
- export declare const TextEditor: ({ characterLimit, error, footer, header, inputHint, isOptional, labelText, namespace, onCancel, onChange, onLinkAdded, onSave, placeholder, previews, readOnly, required, rows, warning, value, ...rest }: TextEditorProps) => React.JSX.Element;
53
+ export declare const TextEditor: ({ characterLimit, error, footer, header, inputHint, isOptional, labelText, namespace, onBlur, onCancel, onChange, onFocus, onLinkAdded, onSave, placeholder, previews, readOnly, required, rows, warning, value, ...rest }: TextEditorProps) => React.JSX.Element;
50
54
  export default TextEditor;
@@ -13,7 +13,6 @@ import { $getRoot } from "lexical";
13
13
  import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
14
14
  import { SerializeLexical, validateUrl } from "./__internal__/helpers";
15
15
  import Label from "../../__internal__/label";
16
- import useDebounce from "../../hooks/__internal__/useDebounce";
17
16
  import useLocale from "../../hooks/__internal__/useLocale";
18
17
  import Logger from "../../__internal__/utils/logger";
19
18
  import { COMPONENT_PREFIX, markdownNodes, theme } from "./__internal__/constants";
@@ -34,8 +33,10 @@ export const TextEditor = ({
34
33
  isOptional = false,
35
34
  labelText,
36
35
  namespace = COMPONENT_PREFIX,
36
+ onBlur,
37
37
  onCancel,
38
38
  onChange,
39
+ onFocus,
39
40
  onLinkAdded,
40
41
  onSave,
41
42
  placeholder,
@@ -74,7 +75,6 @@ export const TextEditor = ({
74
75
  return cleanup;
75
76
  }, [contentEditorRef]);
76
77
  const [cancelTrigger, setCancelTrigger] = useState(false);
77
- const debounceWaitTime = 500;
78
78
  const initialConfig = useMemo(() => {
79
79
  return {
80
80
  namespace,
@@ -88,7 +88,7 @@ export const TextEditor = ({
88
88
 
89
89
  // OnChangePlugin is tested separately
90
90
  /* istanbul ignore next */
91
- const handleChange = useDebounce(newState => {
91
+ const handleChange = useCallback(newState => {
92
92
  const currentTextContent = newState.read(() => $getRoot().getTextContent());
93
93
  if (onChange) {
94
94
  const formattedValues = editorRef.current ? SerializeLexical(editorRef.current) : {};
@@ -101,11 +101,10 @@ export const TextEditor = ({
101
101
  // If the character limit has been exceeded, show the character limit warning
102
102
  setCharacterLimitWarning(currentDiff < 0 ? locale.textEditor.characterLimit(Math.abs(currentDiff)) : undefined);
103
103
  }
104
- }, debounceWaitTime);
104
+ }, [characterLimit, locale.textEditor, onChange]);
105
105
  const handleCancel = useCallback(() => {
106
- const editor = editorRef.current;
107
106
  /* istanbul ignore next */
108
- const isEditable = editor?.isEditable() || false;
107
+ const isEditable = editorRef.current?.isEditable() || false;
109
108
  /* istanbul ignore if */
110
109
  if (!isEditable) return;
111
110
 
@@ -118,13 +117,12 @@ export const TextEditor = ({
118
117
 
119
118
  // Reset the value of the editor when the cancel trigger is updated (implements reset on cancel)
120
119
  useEffect(() => {
121
- const editor = editorRef.current;
122
120
  const safeValue = value || createEmpty();
123
121
 
124
122
  /* istanbul ignore else */
125
- if (editor) {
126
- const newEditorState = editor.parseEditorState(safeValue);
127
- editor.setEditorState(newEditorState);
123
+ if (editorRef.current) {
124
+ const newEditorState = editorRef.current.parseEditorState(safeValue);
125
+ editorRef.current.setEditorState(newEditorState);
128
126
  }
129
127
  }, [cancelTrigger, value]);
130
128
  const toolbarProps = useMemo(() => ({
@@ -133,7 +131,17 @@ export const TextEditor = ({
133
131
  onSave
134
132
  }), [handleCancel, namespace, onCancel, onSave]);
135
133
  return /*#__PURE__*/React.createElement(StyledTextEditorWrapper, _extends({
136
- "data-role": `${namespace}-editor-wrapper`
134
+ "data-role": `${namespace}-editor-wrapper`,
135
+ onBlur: ev => {
136
+ if (!ev.currentTarget.contains(ev.relatedTarget)) {
137
+ onBlur?.(ev);
138
+ }
139
+ },
140
+ onFocus: ev => {
141
+ if (!ev.currentTarget.contains(ev.relatedTarget)) {
142
+ onFocus?.(ev);
143
+ }
144
+ }
137
145
  }, filterStyledSystemMarginProps(rest), tagComponent("text-editor", rest)), /*#__PURE__*/React.createElement(TextEditorContext.Provider, {
138
146
  value: {
139
147
  onLinkAdded
@@ -20,7 +20,7 @@ var _constants = require("./constants");
20
20
  const SerializeLexical = editor => {
21
21
  let htmlString;
22
22
  let json;
23
- editor.update(() => {
23
+ editor.read(() => {
24
24
  // Get the current editor state
25
25
  const editorState = editor.getEditorState();
26
26
  // Serialize the editor state to JSON
@@ -0,0 +1,5 @@
1
+ import { LexicalEditor } from "lexical";
2
+ declare const EditorRefPlugin: ({ setEditorRef, }: {
3
+ setEditorRef: (editor: LexicalEditor) => void;
4
+ }) => null;
5
+ export default EditorRefPlugin;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = require("react");
8
+ var _LexicalComposerContext = require("@lexical/react/LexicalComposerContext");
9
+ /**
10
+ * This plugin allows retrieval of a reference to the current editor. It's useful
11
+ * for testing purposes, where tests might need to directly interact with the editor to
12
+ * emulate e.g. blurring.
13
+ */
14
+
15
+ const EditorRefPlugin = ({
16
+ setEditorRef
17
+ }) => {
18
+ const [editor] = (0, _LexicalComposerContext.useLexicalComposerContext)();
19
+ (0, _react.useEffect)(() => {
20
+ setEditorRef(editor);
21
+ }, [editor, setEditorRef]);
22
+ return null;
23
+ };
24
+ var _default = exports.default = EditorRefPlugin;
@@ -0,0 +1 @@
1
+ export { default } from "./editor-ref.plugin";
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "default", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _editorRef.default;
10
+ }
11
+ });
12
+ var _editorRef = _interopRequireDefault(require("./editor-ref.plugin"));
13
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sideEffects": false,
3
+ "module": "../../../../../../esm/components/text-editor/__internal__/plugins/EditorRef/index.js",
4
+ "main": "./index.js",
5
+ "types": "./index.d.ts"
6
+ }
@@ -94,7 +94,6 @@ const Toolbar = ({
94
94
  "aria-label": locale.textEditor.toolbarAriaLabel(),
95
95
  "data-role": `${namespace}-toolbar`,
96
96
  id: `${namespace}-toolbar`,
97
- onFocus: e => e.stopPropagation(),
98
97
  ref: toolbarRef
99
98
  }, /*#__PURE__*/_react.default.createElement(_toolbar.FormattingButtons, {
100
99
  "data-role": `${namespace}-formatting-buttons`
@@ -23,10 +23,14 @@ export interface TextEditorProps extends MarginProps, TagProps {
23
23
  labelText: string;
24
24
  /** The identifier for the Text Editor. This allows for the using of multiple Text Editors on a screen */
25
25
  namespace?: string;
26
+ /** The callback to fire when the editor loses focus */
27
+ onBlur?: (ev: React.FocusEvent<HTMLElement>) => void;
26
28
  /** The callback to fire when the Cancel button within the editor is pressed */
27
29
  onCancel?: () => void;
28
30
  /** The callback to fire when a change is registered within the editor */
29
31
  onChange?: (value: string, formattedValues: EditorFormattedValues) => void;
32
+ /** The callback to fire when the editor gains focus */
33
+ onFocus?: (ev: React.FocusEvent<HTMLElement>) => void;
30
34
  /** The callback to fire when a link is added into the editor */
31
35
  onLinkAdded?: (link: string, state: string) => void;
32
36
  /** The callback to fire when the Save button within the editor is pressed */
@@ -46,5 +50,5 @@ export interface TextEditorProps extends MarginProps, TagProps {
46
50
  /** The initial value of the editor, as a HTML string, or JSON */
47
51
  value?: string | undefined;
48
52
  }
49
- export declare const TextEditor: ({ characterLimit, error, footer, header, inputHint, isOptional, labelText, namespace, onCancel, onChange, onLinkAdded, onSave, placeholder, previews, readOnly, required, rows, warning, value, ...rest }: TextEditorProps) => React.JSX.Element;
53
+ export declare const TextEditor: ({ characterLimit, error, footer, header, inputHint, isOptional, labelText, namespace, onBlur, onCancel, onChange, onFocus, onLinkAdded, onSave, placeholder, previews, readOnly, required, rows, warning, value, ...rest }: TextEditorProps) => React.JSX.Element;
50
54
  export default TextEditor;
@@ -17,7 +17,6 @@ var _lexical = require("lexical");
17
17
  var _react = _interopRequireWildcard(require("react"));
18
18
  var _helpers = require("./__internal__/helpers");
19
19
  var _label = _interopRequireDefault(require("../../__internal__/label"));
20
- var _useDebounce = _interopRequireDefault(require("../../hooks/__internal__/useDebounce"));
21
20
  var _useLocale = _interopRequireDefault(require("../../hooks/__internal__/useLocale"));
22
21
  var _logger = _interopRequireDefault(require("../../__internal__/utils/logger"));
23
22
  var _constants = require("./__internal__/constants");
@@ -42,8 +41,10 @@ const TextEditor = ({
42
41
  isOptional = false,
43
42
  labelText,
44
43
  namespace = _constants.COMPONENT_PREFIX,
44
+ onBlur,
45
45
  onCancel,
46
46
  onChange,
47
+ onFocus,
47
48
  onLinkAdded,
48
49
  onSave,
49
50
  placeholder,
@@ -82,7 +83,6 @@ const TextEditor = ({
82
83
  return cleanup;
83
84
  }, [contentEditorRef]);
84
85
  const [cancelTrigger, setCancelTrigger] = (0, _react.useState)(false);
85
- const debounceWaitTime = 500;
86
86
  const initialConfig = (0, _react.useMemo)(() => {
87
87
  return {
88
88
  namespace,
@@ -96,7 +96,7 @@ const TextEditor = ({
96
96
 
97
97
  // OnChangePlugin is tested separately
98
98
  /* istanbul ignore next */
99
- const handleChange = (0, _useDebounce.default)(newState => {
99
+ const handleChange = (0, _react.useCallback)(newState => {
100
100
  const currentTextContent = newState.read(() => (0, _lexical.$getRoot)().getTextContent());
101
101
  if (onChange) {
102
102
  const formattedValues = editorRef.current ? (0, _helpers.SerializeLexical)(editorRef.current) : {};
@@ -109,11 +109,10 @@ const TextEditor = ({
109
109
  // If the character limit has been exceeded, show the character limit warning
110
110
  setCharacterLimitWarning(currentDiff < 0 ? locale.textEditor.characterLimit(Math.abs(currentDiff)) : undefined);
111
111
  }
112
- }, debounceWaitTime);
112
+ }, [characterLimit, locale.textEditor, onChange]);
113
113
  const handleCancel = (0, _react.useCallback)(() => {
114
- const editor = editorRef.current;
115
114
  /* istanbul ignore next */
116
- const isEditable = editor?.isEditable() || false;
115
+ const isEditable = editorRef.current?.isEditable() || false;
117
116
  /* istanbul ignore if */
118
117
  if (!isEditable) return;
119
118
 
@@ -126,13 +125,12 @@ const TextEditor = ({
126
125
 
127
126
  // Reset the value of the editor when the cancel trigger is updated (implements reset on cancel)
128
127
  (0, _react.useEffect)(() => {
129
- const editor = editorRef.current;
130
128
  const safeValue = value || (0, _utils.createEmpty)();
131
129
 
132
130
  /* istanbul ignore else */
133
- if (editor) {
134
- const newEditorState = editor.parseEditorState(safeValue);
135
- editor.setEditorState(newEditorState);
131
+ if (editorRef.current) {
132
+ const newEditorState = editorRef.current.parseEditorState(safeValue);
133
+ editorRef.current.setEditorState(newEditorState);
136
134
  }
137
135
  }, [cancelTrigger, value]);
138
136
  const toolbarProps = (0, _react.useMemo)(() => ({
@@ -141,7 +139,17 @@ const TextEditor = ({
141
139
  onSave
142
140
  }), [handleCancel, namespace, onCancel, onSave]);
143
141
  return /*#__PURE__*/_react.default.createElement(_textEditor2.StyledTextEditorWrapper, _extends({
144
- "data-role": `${namespace}-editor-wrapper`
142
+ "data-role": `${namespace}-editor-wrapper`,
143
+ onBlur: ev => {
144
+ if (!ev.currentTarget.contains(ev.relatedTarget)) {
145
+ onBlur?.(ev);
146
+ }
147
+ },
148
+ onFocus: ev => {
149
+ if (!ev.currentTarget.contains(ev.relatedTarget)) {
150
+ onFocus?.(ev);
151
+ }
152
+ }
145
153
  }, (0, _utils2.filterStyledSystemMarginProps)(rest), (0, _tags.default)("text-editor", rest)), /*#__PURE__*/_react.default.createElement(_textEditor.default.Provider, {
146
154
  value: {
147
155
  onLinkAdded
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "carbon-react",
3
- "version": "153.8.0",
3
+ "version": "154.0.0",
4
4
  "description": "A library of reusable React components for easily building user interfaces.",
5
5
  "files": [
6
6
  "lib",