@datarobot/design-system 28.3.4 → 28.5.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.
Files changed (38) hide show
  1. package/cjs/chat/chat-message-body.js +9 -4
  2. package/cjs/chat/hocs.d.ts +1 -1
  3. package/cjs/chat/hocs.js +21 -22
  4. package/cjs/chat/index.d.ts +2 -0
  5. package/cjs/chat/index.js +14 -0
  6. package/cjs/text-editor/inline-text-editor.d.ts +3 -1
  7. package/cjs/text-editor/inline-text-editor.js +6 -2
  8. package/cjs/text-editor/text-editor-constants.d.ts +0 -2
  9. package/cjs/text-editor/text-editor-constants.js +0 -8
  10. package/cjs/text-editor/text-editor-content.d.ts +2 -1
  11. package/cjs/text-editor/text-editor-content.js +22 -2
  12. package/cjs/text-editor/text-editor-header.js +2 -9
  13. package/cjs/text-editor/text-editor-helpers.d.ts +1 -0
  14. package/cjs/text-editor/text-editor-helpers.js +22 -17
  15. package/cjs/text-editor/text-editor.d.ts +5 -1
  16. package/cjs/text-editor/text-editor.js +31 -6
  17. package/esm/chat/chat-message-body.js +9 -4
  18. package/esm/chat/hocs.d.ts +1 -1
  19. package/esm/chat/hocs.js +21 -22
  20. package/esm/chat/index.d.ts +2 -0
  21. package/esm/chat/index.js +2 -0
  22. package/esm/text-editor/inline-text-editor.d.ts +3 -1
  23. package/esm/text-editor/inline-text-editor.js +6 -2
  24. package/esm/text-editor/text-editor-constants.d.ts +0 -2
  25. package/esm/text-editor/text-editor-constants.js +0 -6
  26. package/esm/text-editor/text-editor-content.d.ts +2 -1
  27. package/esm/text-editor/text-editor-content.js +22 -2
  28. package/esm/text-editor/text-editor-header.js +3 -10
  29. package/esm/text-editor/text-editor-helpers.d.ts +1 -0
  30. package/esm/text-editor/text-editor-helpers.js +22 -18
  31. package/esm/text-editor/text-editor.d.ts +5 -1
  32. package/esm/text-editor/text-editor.js +33 -8
  33. package/js/bundle/bundle.js +175 -127
  34. package/js/bundle/bundle.min.js +1 -1
  35. package/js/bundle/index.d.ts +22 -5
  36. package/package.json +1 -1
  37. package/styles/index.css +16 -0
  38. package/styles/index.min.css +1 -1
@@ -1,4 +1,5 @@
1
1
  import React, { useCallback, useContext } from 'react';
2
+ import { Editor } from 'slate';
2
3
  import classNames from 'classnames';
3
4
  import TextEditorContext from './text-editor-context';
4
5
  import { BLOCK_TYPES, MARK_TYPES } from './text-editor-constants';
@@ -146,14 +147,17 @@ export default function TextEditorContent({
146
147
  elementRender = Element,
147
148
  leafRender = Leaf,
148
149
  onBlur = () => {},
149
- onFocus = () => {}
150
+ onFocus = () => {},
151
+ maxLength
150
152
  }) {
151
153
  const {
152
154
  slateReact
153
155
  } = useContext(TextEditorContext);
154
156
  const {
155
- Editable
157
+ Editable,
158
+ useSlate
156
159
  } = slateReact;
160
+ const editor = useSlate();
157
161
  /* eslint-disable-next-line testing-library/render-result-naming-convention */
158
162
  const ElementNode = elementRender;
159
163
  /* eslint-disable-next-line testing-library/render-result-naming-convention */
@@ -190,6 +194,22 @@ export default function TextEditorContent({
190
194
  renderLeaf: renderLeaf,
191
195
  onBlur: onBlur,
192
196
  onFocus: onFocus,
197
+ onDOMBeforeInput: event => {
198
+ // NOTE: copy-paste events handled in withMaxLength() plugin because Slate under some
199
+ // circumstances stops triggering onDOMBeforeInput event for copy-paste ¯\_(ツ)_/¯
200
+
201
+ // no need to do any calculations if maxLength is not set or if it's a delete event
202
+ if (maxLength && !event.inputType.startsWith('delete')) {
203
+ const currentTextLength = Editor.string(editor, []).length;
204
+ let newText = '';
205
+ if (!!event.data) {
206
+ newText = event.data;
207
+ }
208
+ if (currentTextLength + newText.length > maxLength) {
209
+ event.preventDefault();
210
+ }
211
+ }
212
+ },
193
213
  ...ariaAttrs
194
214
  })
195
215
  });
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useContext } from 'react';
1
+ import React, { useContext } from 'react';
2
2
  import { faTrashCan } from '@fortawesome/free-solid-svg-icons/faTrashCan';
3
3
  import { useTranslation } from '../hooks/use-translation';
4
4
  import { ACCENT_TYPES, Button } from '../button';
@@ -21,7 +21,6 @@ export default function TextEditorHeader({
21
21
  const {
22
22
  t
23
23
  } = useTranslation();
24
- const [isDelete, setIsDelete] = useState(false);
25
24
  const {
26
25
  slate,
27
26
  slateReact
@@ -33,13 +32,6 @@ export default function TextEditorHeader({
33
32
  const editor = slateReact.useSlate();
34
33
  const hasReadOnlyTitle = title && !isEditable;
35
34
  const hasEditableTitle = isEditable && onChangeTitle;
36
- useEffect(() => {
37
- if (isDelete) {
38
- onChangeTitle('');
39
- onDelete();
40
- setIsDelete(false);
41
- }
42
- }, [isDelete]);
43
35
  return /*#__PURE__*/_jsx("header", {
44
36
  className: "text-editor-header",
45
37
  "test-id": "text-editor-header",
@@ -63,7 +55,8 @@ export default function TextEditorHeader({
63
55
  onClick: () => {
64
56
  textEditorService.deleteContent(editor);
65
57
  ReactEditor.focus(editor);
66
- setIsDelete(true);
58
+ onChangeTitle('');
59
+ onDelete();
67
60
  },
68
61
  accentType: ACCENT_TYPES.ROUND_ICON,
69
62
  tooltipText: t('Delete'),
@@ -10,6 +10,7 @@ export declare function isEmptyTextEditorValue(value: TextEditorValue[]): boolea
10
10
  * @returns {boolean}
11
11
  */
12
12
  export declare function isLongerTextEditorValueThan(value: TextEditorValue[] | TextEditorValueChild[], maxLength?: number): boolean;
13
+ export declare function getTextEditorValueLength(value: TextEditorValue[] | TextEditorValueChild[]): number;
13
14
  /**
14
15
  * @param {Array} value - the type of value is defined with TextEditorValue
15
16
  * @param {number} maxTextLines
@@ -1,5 +1,5 @@
1
1
  import isEqual from 'lodash/isEqual';
2
- import { BLOCK_TYPES, TEXT_EDITOR_DEFAULT_VALUE, instanceOfContent, instanceOfContentChild } from './text-editor-constants';
2
+ import { BLOCK_TYPES, TEXT_EDITOR_DEFAULT_VALUE } from './text-editor-constants';
3
3
 
4
4
  /**
5
5
  * @param {Array} value - the type of value is defined with TextEditorValue
@@ -17,29 +17,33 @@ export function isEmptyTextEditorValue(value) {
17
17
  export function isLongerTextEditorValueThan(value, maxLength = 120) {
18
18
  let length = 0;
19
19
  for (const record of value) {
20
- if (instanceOfContentChild(record) && !!record.text) {
21
- length += record.text.length;
22
- }
23
- if (instanceOfContent(record) && !!record.children) {
24
- for (const childRecord of record.children) {
25
- if (!!childRecord.text) {
26
- length += childRecord.text.length;
27
- }
28
-
29
- // We already know the result
30
- if (length > maxLength) {
31
- break;
32
- }
33
- }
34
- }
35
-
36
- // We already know the result
20
+ // if we reached maxLength - no need to continue
37
21
  if (length > maxLength) {
38
22
  break;
39
23
  }
24
+ // if this is leaf node that has text - sum it up
25
+ if (record.hasOwnProperty('text') && !!record.text) {
26
+ length += record?.text?.length ?? 0;
27
+ } else if (record.hasOwnProperty('children') && !!record.children) {
28
+ // if this is a node that has children - calculate length of children
29
+ length += getTextEditorValueLength(record.children);
30
+ }
40
31
  }
41
32
  return length > maxLength;
42
33
  }
34
+ export function getTextEditorValueLength(value) {
35
+ let length = 0;
36
+ for (const record of value) {
37
+ // if this is leaf node that has text - sum it up
38
+ if (record.hasOwnProperty('text') && !!record.text) {
39
+ length += record?.text?.length ?? 0;
40
+ } else if (record.hasOwnProperty('children') && !!record.children) {
41
+ // if this is a node that has children - iterate through them via recursion
42
+ length += getTextEditorValueLength(record.children);
43
+ }
44
+ }
45
+ return length;
46
+ }
43
47
 
44
48
  /**
45
49
  * @param {Array} value - the type of value is defined with TextEditorValue
@@ -38,6 +38,8 @@ export type TextEditorComponentProps = {
38
38
  onBlur?: () => void;
39
39
  deleteEditorContentButtonAriaLabel: string;
40
40
  id: string;
41
+ maxLength?: number;
42
+ getCharactersCounterString?: (count: number) => string;
41
43
  };
42
44
  export type TextEditorProps = {
43
45
  name: string;
@@ -94,9 +96,11 @@ export type TextEditorProps = {
94
96
  linkInputLabelText?: string;
95
97
  deleteEditorContentButtonAriaLabel: string;
96
98
  id?: string;
99
+ maxLength?: number;
100
+ getCharactersCounterString?: (count: number) => string;
97
101
  };
98
102
  /**
99
103
  * @midnight-gray-supported
100
104
  * @alpine-light-supported
101
105
  * */
102
- export default function TextEditor({ name, header, type, value, initialValue, validity, label, placeholder, autoFocus, readOnly, testId, contentTestId, contentAriaLabel, className, height, enabledTools, toolbarPosition, elementRender, leafRender, onChange, onCancel, onSave, tooltipTextBold, tooltipTextItalic, tooltipTextUnderline, tooltipTextSuperscript, tooltipTextLink, tooltipTextRemoveLink, tooltipTextBulletedList, tooltipTextNumberedList, tooltipTextClearFormatting, tooltipTextCode, urlIsRequiredMessage, enterValidUrlMessage, saveButtonText, headingLargeText, headingMediumText, headingSmallText, regularText, validationErrorWarningMessage, linkInputLabelText, deleteEditorContentButtonAriaLabel, id, }: TextEditorProps): import("react/jsx-runtime").JSX.Element;
106
+ export default function TextEditor({ name, header, type, value, initialValue, validity, label, placeholder, autoFocus, readOnly, testId, contentTestId, contentAriaLabel, className, height, enabledTools, toolbarPosition, elementRender, leafRender, onChange, onCancel, onSave, tooltipTextBold, tooltipTextItalic, tooltipTextUnderline, tooltipTextSuperscript, tooltipTextLink, tooltipTextRemoveLink, tooltipTextBulletedList, tooltipTextNumberedList, tooltipTextClearFormatting, tooltipTextCode, urlIsRequiredMessage, enterValidUrlMessage, saveButtonText, headingLargeText, headingMediumText, headingSmallText, regularText, validationErrorWarningMessage, linkInputLabelText, deleteEditorContentButtonAriaLabel, id, maxLength, getCharactersCounterString, }: TextEditorProps): import("react/jsx-runtime").JSX.Element;
@@ -1,9 +1,9 @@
1
- import React, { useMemo, useRef, useContext, useState, useEffect } from 'react';
1
+ import React, { useMemo, useRef, useContext, useState, useEffect, useCallback } from 'react';
2
2
  import classnames from 'classnames';
3
3
  import isEqual from 'lodash-es/isEqual';
4
4
  import { withHistory } from 'slate-history';
5
5
  import { ValidityMessages, getErrorAriaAttributes, useDefaultValidationValues } from '../form-field';
6
- import { resetNodes } from '../chat';
6
+ import { resetNodes, CharactersCounter, withMaxLength } from '../chat';
7
7
  import { TextEditorToolbar } from './text-editor-toolbar';
8
8
  import TextEditorContext from './text-editor-context';
9
9
  import TextEditorHeader from './text-editor-header';
@@ -12,6 +12,8 @@ import withLinks from './hooks/with-links';
12
12
  import TextEditorLoader from './text-editor-loader';
13
13
  import { TEXT_EDITOR_DEFAULT_VALUE, TEXT_EDITOR_DEFAULT_TOOLS, TOOLBAR_POSITION, INLINE_EDITOR_TYPES, HEADER_HEIGHT } from './text-editor-constants';
14
14
  import { useTextEditorDefaultTranslations } from './text-editor-hooks';
15
+ import { getTextEditorValueLength } from './text-editor-helpers';
16
+ import { useTranslation } from '../hooks/use-translation';
15
17
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
16
18
  function TextEditorComponent({
17
19
  name,
@@ -38,8 +40,13 @@ function TextEditorComponent({
38
40
  leafRender = ContentLeaf,
39
41
  onChange = () => {},
40
42
  deleteEditorContentButtonAriaLabel,
41
- id = ''
43
+ id = '',
44
+ maxLength,
45
+ getCharactersCounterString
42
46
  }) {
47
+ const {
48
+ t
49
+ } = useTranslation();
43
50
  const {
44
51
  slate,
45
52
  slateReact
@@ -54,7 +61,15 @@ function TextEditorComponent({
54
61
  withReact,
55
62
  Slate
56
63
  } = slateReact;
57
- const editor = useMemo(() => withLinks(withHistory(withReact(createEditor()))), []);
64
+ const getCharactersCounterStringText = useCallback(count => {
65
+ if (getCharactersCounterString) {
66
+ return getCharactersCounterString(count);
67
+ }
68
+ return t('{{count}} characters remaining', {
69
+ count
70
+ });
71
+ }, [getCharactersCounterString]);
72
+ const editor = useMemo(() => withMaxLength(maxLength)(withLinks(withHistory(withReact(createEditor())))), [maxLength]);
58
73
  const selection = useRef(null);
59
74
  const slateValue = value || initialValue || TEXT_EDITOR_DEFAULT_VALUE;
60
75
  useEffect(() => {
@@ -119,7 +134,8 @@ function TextEditorComponent({
119
134
  hasDeleteButton: header.hasDeleteButton,
120
135
  onDelete: header.onDelete,
121
136
  deleteEditorContentButtonAriaLabel: deleteEditorContentButtonAriaLabel,
122
- inputAriaLabel: header.inputAriaLabel
137
+ inputAriaLabel: header.inputAriaLabel,
138
+ maxLength: header.maxLength
123
139
  }), toolbarPosition === TOOLBAR_POSITION.TOP && toolbarMemo, /*#__PURE__*/_jsx(TextEditorContent, {
124
140
  placeholder: placeholder,
125
141
  readOnly: readOnly,
@@ -129,9 +145,14 @@ function TextEditorComponent({
129
145
  leafRender: leafRender,
130
146
  onBlur: saveSelection,
131
147
  onFocus: recoverSelection,
132
- ariaAttrs: ariaAttrs
148
+ ariaAttrs: ariaAttrs,
149
+ maxLength: maxLength
133
150
  }), toolbarPosition === TOOLBAR_POSITION.BOTTOM && toolbarMemo]
134
151
  })
152
+ }), maxLength && /*#__PURE__*/_jsx(CharactersCounter, {
153
+ currLength: getTextEditorValueLength(slateValue),
154
+ maxLength: maxLength,
155
+ getCharactersCounterString: getCharactersCounterStringText
135
156
  }), validity && /*#__PURE__*/_jsx(ValidityMessages, {
136
157
  validity: validity,
137
158
  validationTestId: `${name}-validity`,
@@ -186,7 +207,9 @@ export default function TextEditor({
186
207
  validationErrorWarningMessage,
187
208
  linkInputLabelText,
188
209
  deleteEditorContentButtonAriaLabel,
189
- id = ''
210
+ id = '',
211
+ maxLength,
212
+ getCharactersCounterString
190
213
  }) {
191
214
  const [isFocused, setIsFocused] = useState(false);
192
215
  const defaultTranslations = useTextEditorDefaultTranslations();
@@ -244,7 +267,9 @@ export default function TextEditor({
244
267
  leafRender: leafRender,
245
268
  onChange: onChange,
246
269
  deleteEditorContentButtonAriaLabel: deleteEditorContentButtonAriaLabel,
247
- id: id
270
+ id: id,
271
+ maxLength: maxLength,
272
+ getCharactersCounterString: getCharactersCounterString
248
273
  })
249
274
  })
250
275
  });