@squiz/formatted-text-editor 1.35.1-alpha.12 → 1.35.1-alpha.13

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.
package/demo/App.tsx CHANGED
@@ -3,8 +3,20 @@ import { Editor, remirrorNodeToSquizNode, squizNodeToRemirrorNode } from '../src
3
3
  import { RemirrorEventListener, Extension } from '@remirror/core';
4
4
  import ReactDiffViewer from 'react-diff-viewer-continued';
5
5
  import { AppContext } from './AppContext';
6
+ import Button from '../src/ui/Button/Button';
7
+ import TextFieldsOutlinedIcon from '@mui/icons-material/TextFieldsOutlined';
8
+ import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';
9
+ import { VerticalDivider } from '@remirror/react-components';
10
+ const ComponentHandlers = () => (
11
+ <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '5px' }}>
12
+ <Button icon={<TextFieldsOutlinedIcon />} onClick={(x) => x} />
13
+ <VerticalDivider />
14
+ <Button icon={<DeleteOutlineOutlinedIcon />} onClick={(x) => x} />
15
+ </div>
16
+ );
6
17
 
7
18
  function App() {
19
+ const [showChildren] = useState(false);
8
20
  const [doc, setDoc] = useState('');
9
21
  const [squizDoc, setSquizDoc] = useState('');
10
22
  const [reconvertedDoc, setReconvertedDoc] = useState('');
@@ -41,7 +53,9 @@ function App() {
41
53
  editable={editable}
42
54
  content={`<p>Hello <a href="https://www.google.com"><strong>Mr Bean</strong></a>, nice to <a href="https://www.google.com">meet you</a>.<img src="https://media2.giphy.com/media/3o6ozsIxg5legYvggo/giphy.gif" height="150" width="200"/></p>`}
43
55
  onChange={handleEditorChange}
44
- />
56
+ >
57
+ {showChildren && <ComponentHandlers />}
58
+ </Editor>
45
59
  </AppContext>
46
60
  </div>
47
61
  <h1>Document</h1>
@@ -1,8 +1,10 @@
1
+ import { ReactNode } from 'react';
1
2
  import { RemirrorContentType, RemirrorEventListener, Extension } from '@remirror/core';
2
3
  type EditorProps = {
3
4
  content?: RemirrorContentType;
4
5
  onChange?: RemirrorEventListener<Extension>;
5
6
  editable?: boolean;
7
+ children?: ReactNode;
6
8
  };
7
- declare const Editor: ({ content, editable, onChange }: EditorProps) => JSX.Element;
9
+ declare const Editor: ({ content, editable, onChange, children }: EditorProps) => JSX.Element;
8
10
  export default Editor;
@@ -28,10 +28,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  const react_1 = __importStar(require("react"));
30
30
  const react_2 = require("@remirror/react");
31
+ const clsx_1 = __importDefault(require("clsx"));
31
32
  const EditorToolbar_1 = require("../EditorToolbar");
32
33
  const EditorContext_1 = require("./EditorContext");
33
34
  const Extensions_1 = require("../Extensions/Extensions");
34
- const clsx_1 = __importDefault(require("clsx"));
35
+ const useFocus_1 = __importDefault(require("../hooks/useFocus"));
35
36
  const WrappedEditor = () => {
36
37
  const preventImagePaste = (0, react_1.useCallback)((event) => {
37
38
  const { clipboardData } = event;
@@ -45,7 +46,7 @@ const WrappedEditor = () => {
45
46
  (0, react_2.useEditorEvent)('paste', preventImagePaste);
46
47
  return react_1.default.createElement(react_2.EditorComponent, null);
47
48
  };
48
- const Editor = ({ content, editable = true, onChange }) => {
49
+ const Editor = ({ content, editable = true, onChange, children }) => {
49
50
  const { manager, state, setState } = (0, react_2.useRemirror)({
50
51
  extensions: (0, Extensions_1.createExtensions)((0, react_1.useContext)(EditorContext_1.EditorContext)),
51
52
  content,
@@ -56,10 +57,12 @@ const Editor = ({ content, editable = true, onChange }) => {
56
57
  setState(parameter.state);
57
58
  onChange?.(parameter);
58
59
  };
60
+ const { isEditorFocused, handleFocus, handleBlur } = (0, useFocus_1.default)();
59
61
  return (react_1.default.createElement("div", { className: "squiz-fte-scope" },
60
- react_1.default.createElement("div", { className: (0, clsx_1.default)('remirror-theme formatted-text-editor', !editable && 'formatted-text-editor--is-disabled') },
62
+ react_1.default.createElement("div", { onBlur: handleBlur, onFocus: handleFocus, className: (0, clsx_1.default)('remirror-theme formatted-text-editor', !editable && 'formatted-text-editor--is-disabled') },
61
63
  react_1.default.createElement(react_2.Remirror, { manager: manager, state: state, editable: editable, onChange: handleChange, placeholder: "Write something", label: "Text editor" },
62
- editable && react_1.default.createElement(EditorToolbar_1.Toolbar, null),
64
+ editable && react_1.default.createElement(EditorToolbar_1.Toolbar, { isEditorFocused: isEditorFocused }),
65
+ children,
63
66
  react_1.default.createElement(WrappedEditor, null),
64
67
  editable && react_1.default.createElement(EditorToolbar_1.FloatingToolbar, null)))));
65
68
  };
@@ -1 +1,5 @@
1
- export declare const Toolbar: () => JSX.Element;
1
+ type ToolbarProps = {
2
+ isEditorFocused: boolean;
3
+ };
4
+ export declare const Toolbar: ({ isEditorFocused }: ToolbarProps) => JSX.Element;
5
+ export {};
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Toolbar = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const react_components_1 = require("@remirror/react-components");
9
+ const clsx_1 = __importDefault(require("clsx"));
9
10
  const ItalicButton_1 = __importDefault(require("./Tools/Italic/ItalicButton"));
10
11
  const UnderlineButton_1 = __importDefault(require("./Tools/Underline/UnderlineButton"));
11
12
  const BoldButton_1 = __importDefault(require("./Tools/Bold/BoldButton"));
@@ -13,14 +14,14 @@ const TextAlignButtons_1 = __importDefault(require("./Tools/TextAlign/TextAlignB
13
14
  const UndoButton_1 = __importDefault(require("./Tools/Undo/UndoButton"));
14
15
  const RedoButton_1 = __importDefault(require("./Tools/Redo/RedoButton"));
15
16
  const TextTypeDropdown_1 = __importDefault(require("./Tools/TextType/TextTypeDropdown"));
16
- const hooks_1 = require("../hooks");
17
17
  const LinkButton_1 = __importDefault(require("./Tools/Link/LinkButton"));
18
18
  const ImageButton_1 = __importDefault(require("./Tools/Image/ImageButton"));
19
19
  const RemoveLinkButton_1 = __importDefault(require("./Tools/Link/RemoveLinkButton"));
20
20
  const ClearFormattingButton_1 = __importDefault(require("./Tools/ClearFormatting/ClearFormattingButton"));
21
- const Toolbar = () => {
21
+ const hooks_1 = require("../hooks");
22
+ const Toolbar = ({ isEditorFocused }) => {
22
23
  const extensionNames = (0, hooks_1.useExtensionNames)();
23
- return (react_1.default.createElement(react_components_1.Toolbar, { className: "remirror-toolbar editor-toolbar" },
24
+ return (react_1.default.createElement(react_components_1.Toolbar, { className: (0, clsx_1.default)('remirror-toolbar editor-toolbar', isEditorFocused === true ? 'show' : 'hide') },
24
25
  extensionNames.history && (react_1.default.createElement(react_1.default.Fragment, null,
25
26
  react_1.default.createElement(UndoButton_1.default, null),
26
27
  react_1.default.createElement(RedoButton_1.default, null),
@@ -0,0 +1,6 @@
1
+ declare const useEditorFocus: () => {
2
+ isEditorFocused: boolean;
3
+ handleFocus: () => void;
4
+ handleBlur: () => void;
5
+ };
6
+ export default useEditorFocus;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const react_1 = require("react");
4
+ const useEditorFocus = () => {
5
+ const [isEditorFocused, setIsEditorFocused] = (0, react_1.useState)(false);
6
+ const handleFocus = (0, react_1.useCallback)(() => {
7
+ setIsEditorFocused(true);
8
+ }, []);
9
+ const handleBlur = (0, react_1.useCallback)(() => {
10
+ setIsEditorFocused(false);
11
+ }, []);
12
+ return {
13
+ isEditorFocused,
14
+ handleFocus,
15
+ handleBlur,
16
+ };
17
+ };
18
+ exports.default = useEditorFocus;
package/lib/index.css CHANGED
@@ -835,6 +835,14 @@
835
835
  .squiz-fte-scope .formatted-text-editor img {
836
836
  display: inline;
837
837
  }
838
+ .formatted-text-editor:has(.editor-toolbar.show, .show.squiz-fte-scope__floating-popover) {
839
+ --tw-shadow: 0 0 0 1px rgba(0,0,0,0.04), 0 1px 24px 12px rgba(0,0,0,0.12);
840
+ --tw-shadow-colored: 0 0 0 1px var(--tw-shadow-color), 0 1px 24px 12px var(--tw-shadow-color);
841
+ box-shadow:
842
+ var(--tw-ring-offset-shadow, 0 0 #0000),
843
+ var(--tw-ring-shadow, 0 0 #0000),
844
+ var(--tw-shadow);
845
+ }
838
846
  .squiz-fte-scope .editor-toolbar,
839
847
  .squiz-fte-scope__floating-popover {
840
848
  border-bottom-width: 2px;
@@ -846,6 +854,7 @@
846
854
  padding: 0.25rem;
847
855
  display: flex;
848
856
  justify-items: center;
857
+ flex-wrap: wrap;
849
858
  }
850
859
  .squiz-fte-scope .editor-toolbar > *:not(:first-child, .MuiDivider-root),
851
860
  .squiz-fte-scope__floating-popover > *:not(:first-child, .MuiDivider-root) {
@@ -857,6 +866,8 @@
857
866
  margin-bottom: -0.25rem;
858
867
  margin-left: 0.25rem;
859
868
  margin-right: 0.25rem;
869
+ margin-top: 0px;
870
+ margin-bottom: 0px;
860
871
  border-width: 1px;
861
872
  margin-right: 2px;
862
873
  height: auto;
@@ -870,6 +881,18 @@
870
881
  .squiz-fte-scope__floating-popover .squiz-fte-btn ~ .squiz-fte-btn {
871
882
  margin-left: 2px;
872
883
  }
884
+ .squiz-fte-scope .editor-toolbar.show,
885
+ .show.squiz-fte-scope__floating-popover {
886
+ max-height: 200px;
887
+ opacity: 1;
888
+ transition: max-height 0.3s ease, opacity 0.4s ease;
889
+ }
890
+ .squiz-fte-scope .editor-toolbar.hide,
891
+ .hide.squiz-fte-scope__floating-popover {
892
+ max-height: 0;
893
+ opacity: 0;
894
+ transition: max-height 0.3s ease, opacity 0.4s ease;
895
+ }
873
896
  .squiz-fte-scope__floating-popover {
874
897
  display: flex;
875
898
  border-radius: 6px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/formatted-text-editor",
3
- "version": "1.35.1-alpha.12",
3
+ "version": "1.35.1-alpha.13",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "scripts": {
@@ -20,8 +20,8 @@
20
20
  "@headlessui/react": "1.7.11",
21
21
  "@mui/icons-material": "5.11.16",
22
22
  "@remirror/react": "2.0.25",
23
- "@squiz/dx-json-schema-lib": "1.35.1-alpha.12",
24
- "@squiz/resource-browser": "1.35.1-alpha.12",
23
+ "@squiz/dx-json-schema-lib": "1.35.1-alpha.13",
24
+ "@squiz/resource-browser": "1.35.1-alpha.13",
25
25
  "clsx": "1.2.1",
26
26
  "react-hook-form": "7.43.2",
27
27
  "react-image-size": "2.0.0",
@@ -75,5 +75,5 @@
75
75
  "volta": {
76
76
  "node": "18.15.0"
77
77
  },
78
- "gitHead": "2dcb733dfca6ef7a1de6ad58cca13496529de539"
78
+ "gitHead": "1bfc8c5f43eb37847e86d3dc77b4e30d203a703b"
79
79
  }
@@ -5,7 +5,23 @@ import '@testing-library/jest-dom';
5
5
  import { renderWithEditor } from '../../tests';
6
6
  import ImageButton from '../EditorToolbar/Tools/Image/ImageButton';
7
7
 
8
+ const isEditorFocused = jest.fn().mockReturnValue(true);
9
+ const handleFocusMock = jest.fn();
10
+ const handleBlurMock = jest.fn();
11
+ jest.mock('../hooks/useFocus', () => ({
12
+ __esModule: true,
13
+ default: () => ({
14
+ isEditorFocused: isEditorFocused(),
15
+ handleFocus: handleFocusMock,
16
+ handleBlur: handleBlurMock,
17
+ }),
18
+ }));
19
+
8
20
  describe('Formatted text editor', () => {
21
+ afterEach(() => {
22
+ jest.clearAllMocks();
23
+ });
24
+
9
25
  it('Renders the text editor', () => {
10
26
  render(<Editor />);
11
27
  expect(screen.getByRole('textbox')).toBeInTheDocument();
@@ -311,4 +327,38 @@ describe('Formatted text editor', () => {
311
327
 
312
328
  expect(buttonLabels).toEqual([]);
313
329
  });
330
+
331
+ it('triggers handleFocus when editor is focused', async () => {
332
+ const { getByLabelText } = render(<Editor />);
333
+ const editorInput = getByLabelText('Text editor');
334
+
335
+ fireEvent.focus(editorInput);
336
+
337
+ expect(handleBlurMock).toHaveBeenCalledTimes(0);
338
+ expect(handleFocusMock).toHaveBeenCalledTimes(1);
339
+ });
340
+
341
+ it('triggers handleBlur when editor is blurred', () => {
342
+ const { getByLabelText } = render(<Editor />);
343
+ const editorInput = getByLabelText('Text editor');
344
+
345
+ fireEvent.blur(editorInput);
346
+
347
+ expect(handleBlurMock).toHaveBeenCalledTimes(1);
348
+ expect(handleFocusMock).toHaveBeenCalledTimes(0);
349
+ });
350
+
351
+ it('should apply hide class when focus hook returns false', () => {
352
+ isEditorFocused.mockReturnValue(false);
353
+ const { container } = render(<Editor />);
354
+
355
+ expect(container.querySelector('.hide')).toBeInTheDocument();
356
+ });
357
+
358
+ it('should apply show class when focus hook returns true', () => {
359
+ isEditorFocused.mockReturnValue(true);
360
+ const { container } = render(<Editor />);
361
+
362
+ expect(container.querySelector('.show')).toBeInTheDocument();
363
+ });
314
364
  });
@@ -1,16 +1,18 @@
1
- import React, { useContext, useCallback } from 'react';
1
+ import React, { useContext, useCallback, ReactNode } from 'react';
2
2
  import { EditorComponent, Remirror, useRemirror, useEditorEvent } from '@remirror/react';
3
3
  import { RemirrorContentType, RemirrorEventListener, Extension } from '@remirror/core';
4
+ import { ClipboardEventHandler } from '@remirror/extension-events/dist-types/events-extension';
5
+ import clsx from 'clsx';
4
6
  import { Toolbar, FloatingToolbar } from '../EditorToolbar';
5
7
  import { EditorContext } from './EditorContext';
6
8
  import { createExtensions } from '../Extensions/Extensions';
7
- import clsx from 'clsx';
8
- import { ClipboardEventHandler } from '@remirror/extension-events/dist-types/events-extension';
9
+ import useEditorFocus from '../hooks/useFocus';
9
10
 
10
11
  type EditorProps = {
11
12
  content?: RemirrorContentType;
12
13
  onChange?: RemirrorEventListener<Extension>;
13
14
  editable?: boolean;
15
+ children?: ReactNode;
14
16
  };
15
17
 
16
18
  const WrappedEditor = () => {
@@ -29,7 +31,7 @@ const WrappedEditor = () => {
29
31
  return <EditorComponent />;
30
32
  };
31
33
 
32
- const Editor = ({ content, editable = true, onChange }: EditorProps) => {
34
+ const Editor = ({ content, editable = true, onChange, children }: EditorProps) => {
33
35
  const { manager, state, setState } = useRemirror({
34
36
  extensions: createExtensions(useContext(EditorContext)),
35
37
  content,
@@ -42,9 +44,15 @@ const Editor = ({ content, editable = true, onChange }: EditorProps) => {
42
44
  onChange?.(parameter);
43
45
  };
44
46
 
47
+ const { isEditorFocused, handleFocus, handleBlur } = useEditorFocus();
48
+
45
49
  return (
46
50
  <div className="squiz-fte-scope">
47
- <div className={clsx('remirror-theme formatted-text-editor', !editable && 'formatted-text-editor--is-disabled')}>
51
+ <div
52
+ onBlur={handleBlur}
53
+ onFocus={handleFocus}
54
+ className={clsx('remirror-theme formatted-text-editor', !editable && 'formatted-text-editor--is-disabled')}
55
+ >
48
56
  <Remirror
49
57
  manager={manager}
50
58
  state={state}
@@ -53,7 +61,8 @@ const Editor = ({ content, editable = true, onChange }: EditorProps) => {
53
61
  placeholder="Write something"
54
62
  label="Text editor"
55
63
  >
56
- {editable && <Toolbar />}
64
+ {editable && <Toolbar isEditorFocused={isEditorFocused} />}
65
+ {children}
57
66
  <WrappedEditor />
58
67
  {editable && <FloatingToolbar />}
59
68
  </Remirror>
@@ -48,4 +48,8 @@
48
48
  img {
49
49
  display: inline;
50
50
  }
51
+
52
+ &:has(.editor-toolbar.show) {
53
+ @apply shadow-lg;
54
+ }
51
55
  }
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Toolbar as RemirrorToolbar, VerticalDivider } from '@remirror/react-components';
3
+ import clsx from 'clsx';
3
4
  import ItalicButton from './Tools/Italic/ItalicButton';
4
5
  import UnderlineButton from './Tools/Underline/UnderlineButton';
5
6
  import BoldButton from './Tools/Bold/BoldButton';
@@ -7,17 +8,20 @@ import TextAlignButtons from './Tools/TextAlign/TextAlignButtons';
7
8
  import UndoButton from './Tools/Undo/UndoButton';
8
9
  import RedoButton from './Tools/Redo/RedoButton';
9
10
  import TextTypeDropdown from './Tools/TextType/TextTypeDropdown';
10
- import { useExtensionNames } from '../hooks';
11
11
  import LinkButton from './Tools/Link/LinkButton';
12
12
  import ImageButton from './Tools/Image/ImageButton';
13
13
  import RemoveLinkButton from './Tools/Link/RemoveLinkButton';
14
14
  import ClearFormattingButton from './Tools/ClearFormatting/ClearFormattingButton';
15
+ import { useExtensionNames } from '../hooks';
15
16
 
16
- export const Toolbar = () => {
17
+ type ToolbarProps = {
18
+ isEditorFocused: boolean;
19
+ };
20
+ export const Toolbar = ({ isEditorFocused }: ToolbarProps) => {
17
21
  const extensionNames = useExtensionNames();
18
22
 
19
23
  return (
20
- <RemirrorToolbar className="remirror-toolbar editor-toolbar">
24
+ <RemirrorToolbar className={clsx('remirror-toolbar editor-toolbar', isEditorFocused === true ? 'show' : 'hide')}>
21
25
  {extensionNames.history && (
22
26
  <>
23
27
  <UndoButton />
@@ -3,20 +3,34 @@
3
3
 
4
4
  display: flex;
5
5
  justify-items: center;
6
+ flex-wrap: wrap;
6
7
 
7
8
  > *:not(:first-child, .MuiDivider-root) {
8
9
  margin: 0 0 0 2px;
9
10
  }
10
11
 
11
12
  .MuiDivider-root {
12
- @apply -my-1 mx-1 border;
13
+ @apply -my-1 mx-1 border mt-0 mb-0;
13
14
  margin-right: 2px;
14
15
  height: auto;
15
16
  }
17
+
16
18
  .squiz-fte-btn {
17
19
  @apply p-1 font-bold;
18
20
  ~ .squiz-fte-btn {
19
21
  margin-left: 2px;
20
22
  }
21
23
  }
24
+
25
+ &.show {
26
+ max-height: 200px;
27
+ opacity: 1;
28
+ transition: max-height 0.3s ease, opacity 0.4s ease;
29
+ }
30
+
31
+ &.hide {
32
+ max-height: 0;
33
+ opacity: 0;
34
+ transition: max-height 0.3s ease, opacity 0.4s ease;
35
+ }
22
36
  }
@@ -0,0 +1,21 @@
1
+ import { useState, useCallback } from 'react';
2
+
3
+ const useEditorFocus = () => {
4
+ const [isEditorFocused, setIsEditorFocused] = useState(false);
5
+
6
+ const handleFocus = useCallback(() => {
7
+ setIsEditorFocused(true);
8
+ }, []);
9
+
10
+ const handleBlur = useCallback(() => {
11
+ setIsEditorFocused(false);
12
+ }, []);
13
+
14
+ return {
15
+ isEditorFocused,
16
+ handleFocus,
17
+ handleBlur,
18
+ };
19
+ };
20
+
21
+ export default useEditorFocus;