@squiz/formatted-text-editor 1.12.0-alpha.9 → 1.12.1-alpha.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 (162) hide show
  1. package/.eslintrc.json +34 -0
  2. package/CHANGELOG.md +48 -0
  3. package/README.md +18 -56
  4. package/build.js +21 -0
  5. package/cypress/e2e/bold.spec.cy.ts +18 -0
  6. package/cypress/global.d.ts +9 -0
  7. package/cypress/support/commands.ts +130 -0
  8. package/cypress/support/e2e.ts +20 -0
  9. package/cypress/tsconfig.json +8 -0
  10. package/cypress.config.ts +7 -0
  11. package/demo/App.tsx +39 -0
  12. package/demo/index.html +13 -0
  13. package/demo/index.scss +40 -0
  14. package/demo/main.tsx +10 -0
  15. package/demo/public/favicon-dxp.svg +3 -0
  16. package/demo/vite-env.d.ts +1 -0
  17. package/file-transformer.js +1 -0
  18. package/jest.bootstrap.ts +3 -0
  19. package/jest.config.ts +30 -0
  20. package/lib/Editor/Editor.d.ts +4 -2
  21. package/lib/Editor/Editor.js +11 -14
  22. package/lib/EditorToolbar/FloatingToolbar.d.ts +1 -0
  23. package/lib/EditorToolbar/FloatingToolbar.js +31 -0
  24. package/lib/EditorToolbar/Toolbar.d.ts +1 -0
  25. package/lib/EditorToolbar/Toolbar.js +25 -0
  26. package/lib/EditorToolbar/Tools/Link/Form/LinkForm.d.ts +10 -0
  27. package/lib/EditorToolbar/Tools/Link/Form/LinkForm.js +23 -0
  28. package/lib/EditorToolbar/Tools/Link/LinkButton.d.ts +5 -0
  29. package/lib/EditorToolbar/Tools/Link/LinkButton.js +34 -0
  30. package/lib/EditorToolbar/Tools/Link/LinkModal.d.ts +8 -0
  31. package/lib/EditorToolbar/Tools/Link/LinkModal.js +14 -0
  32. package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.d.ts +2 -0
  33. package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.js +16 -0
  34. package/lib/EditorToolbar/Tools/Redo/RedoButton.d.ts +2 -0
  35. package/lib/EditorToolbar/Tools/Redo/RedoButton.js +16 -0
  36. package/lib/EditorToolbar/Tools/TextType/Heading/HeadingButton.d.ts +5 -0
  37. package/lib/EditorToolbar/Tools/TextType/Heading/HeadingButton.js +32 -0
  38. package/lib/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.d.ts +2 -0
  39. package/lib/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.js +16 -0
  40. package/lib/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.d.ts +2 -0
  41. package/lib/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.js +16 -0
  42. package/lib/EditorToolbar/Tools/TextType/TextTypeDropdown.d.ts +2 -0
  43. package/lib/EditorToolbar/Tools/TextType/TextTypeDropdown.js +35 -0
  44. package/lib/EditorToolbar/Tools/Undo/UndoButton.d.ts +2 -0
  45. package/lib/EditorToolbar/Tools/Undo/UndoButton.js +16 -0
  46. package/lib/EditorToolbar/index.d.ts +2 -0
  47. package/lib/EditorToolbar/index.js +2 -0
  48. package/lib/Extensions/Extensions.d.ts +4 -0
  49. package/lib/Extensions/Extensions.js +20 -0
  50. package/lib/Extensions/LinkExtension/LinkExtension.d.ts +16 -0
  51. package/lib/Extensions/LinkExtension/LinkExtension.js +91 -0
  52. package/lib/Extensions/PreformattedExtension/PreformattedExtension.d.ts +10 -0
  53. package/lib/Extensions/PreformattedExtension/PreformattedExtension.js +46 -0
  54. package/lib/FormattedTextEditor.d.ts +2 -2
  55. package/lib/FormattedTextEditor.js +1 -6
  56. package/lib/hooks/index.d.ts +1 -0
  57. package/lib/hooks/index.js +1 -0
  58. package/lib/hooks/useExtensionNames.d.ts +1 -0
  59. package/lib/hooks/useExtensionNames.js +12 -0
  60. package/lib/index.css +786 -3689
  61. package/lib/ui/Inputs/Select/Select.d.ts +12 -0
  62. package/lib/ui/Inputs/Select/Select.js +23 -0
  63. package/lib/ui/Inputs/Text/TextInput.d.ts +4 -0
  64. package/lib/ui/Inputs/Text/TextInput.js +7 -0
  65. package/lib/ui/Modal/FormModal.d.ts +5 -0
  66. package/lib/ui/Modal/FormModal.js +11 -0
  67. package/lib/ui/Modal/Modal.d.ts +10 -0
  68. package/lib/ui/Modal/Modal.js +48 -0
  69. package/lib/ui/ToolbarButton/ToolbarButton.d.ts +1 -1
  70. package/lib/ui/ToolbarButton/ToolbarButton.js +1 -1
  71. package/lib/ui/ToolbarDropdown/ToolbarDropdown.d.ts +6 -0
  72. package/lib/ui/ToolbarDropdown/ToolbarDropdown.js +20 -0
  73. package/lib/ui/ToolbarDropdownButton/ToolbarDropdownButton.d.ts +9 -0
  74. package/lib/ui/ToolbarDropdownButton/ToolbarDropdownButton.js +8 -0
  75. package/lib/utils/createToolbarPositioner.d.ts +18 -0
  76. package/lib/utils/createToolbarPositioner.js +81 -0
  77. package/lib/utils/getCursorRect.d.ts +2 -0
  78. package/lib/utils/getCursorRect.js +3 -0
  79. package/package.json +22 -13
  80. package/postcss.config.js +12 -0
  81. package/src/Editor/Editor.mock.tsx +43 -0
  82. package/src/Editor/Editor.spec.tsx +254 -0
  83. package/src/Editor/Editor.tsx +46 -0
  84. package/src/Editor/_editor.scss +82 -0
  85. package/src/EditorToolbar/FloatingToolbar.spec.tsx +30 -0
  86. package/src/EditorToolbar/FloatingToolbar.tsx +40 -0
  87. package/src/EditorToolbar/Toolbar.tsx +33 -0
  88. package/src/EditorToolbar/Tools/Bold/BoldButton.spec.tsx +19 -0
  89. package/src/EditorToolbar/Tools/Bold/BoldButton.tsx +30 -0
  90. package/src/EditorToolbar/Tools/Italic/ItalicButton.spec.tsx +19 -0
  91. package/src/EditorToolbar/Tools/Italic/ItalicButton.tsx +30 -0
  92. package/src/EditorToolbar/Tools/Link/Form/LinkForm.spec.tsx +30 -0
  93. package/src/EditorToolbar/Tools/Link/Form/LinkForm.tsx +48 -0
  94. package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +277 -0
  95. package/src/EditorToolbar/Tools/Link/LinkButton.tsx +56 -0
  96. package/src/EditorToolbar/Tools/Link/LinkModal.tsx +29 -0
  97. package/src/EditorToolbar/Tools/Link/RemoveLinkButton.spec.tsx +46 -0
  98. package/src/EditorToolbar/Tools/Link/RemoveLinkButton.tsx +27 -0
  99. package/src/EditorToolbar/Tools/Redo/RedoButton.spec.tsx +59 -0
  100. package/src/EditorToolbar/Tools/Redo/RedoButton.tsx +30 -0
  101. package/src/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.spec.tsx +39 -0
  102. package/src/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.tsx +31 -0
  103. package/src/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.spec.tsx +39 -0
  104. package/src/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.tsx +31 -0
  105. package/src/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.spec.tsx +39 -0
  106. package/src/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.tsx +31 -0
  107. package/src/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.spec.tsx +39 -0
  108. package/src/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.tsx +31 -0
  109. package/src/EditorToolbar/Tools/TextAlign/TextAlignButtons.tsx +21 -0
  110. package/src/EditorToolbar/Tools/TextType/Heading/HeadingButton.spec.tsx +56 -0
  111. package/src/EditorToolbar/Tools/TextType/Heading/HeadingButton.tsx +52 -0
  112. package/src/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.spec.tsx +30 -0
  113. package/src/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.tsx +25 -0
  114. package/src/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.spec.tsx +47 -0
  115. package/src/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.tsx +30 -0
  116. package/src/EditorToolbar/Tools/TextType/TextTypeDropdown.spec.tsx +51 -0
  117. package/src/EditorToolbar/Tools/TextType/TextTypeDropdown.tsx +44 -0
  118. package/src/EditorToolbar/Tools/Underline/Underline.spec.tsx +19 -0
  119. package/src/EditorToolbar/Tools/Underline/UnderlineButton.tsx +30 -0
  120. package/src/EditorToolbar/Tools/Undo/UndoButton.spec.tsx +49 -0
  121. package/src/EditorToolbar/Tools/Undo/UndoButton.tsx +30 -0
  122. package/src/EditorToolbar/_floating-toolbar.scss +4 -0
  123. package/src/EditorToolbar/_toolbar.scss +16 -0
  124. package/src/EditorToolbar/index.ts +2 -0
  125. package/src/Extensions/Extensions.ts +29 -0
  126. package/src/Extensions/LinkExtension/LinkExtension.ts +116 -0
  127. package/src/Extensions/PreformattedExtension/PreformattedExtension.ts +50 -0
  128. package/src/FormattedTextEditor.spec.tsx +10 -0
  129. package/src/FormattedTextEditor.tsx +3 -0
  130. package/src/hooks/index.ts +1 -0
  131. package/src/hooks/useExtensionNames.ts +15 -0
  132. package/src/index.scss +19 -0
  133. package/src/index.ts +3 -0
  134. package/src/ui/Inputs/Select/Select.spec.tsx +30 -0
  135. package/src/ui/Inputs/Select/Select.tsx +66 -0
  136. package/src/ui/Inputs/Text/TextInput.spec.tsx +43 -0
  137. package/src/ui/Inputs/Text/TextInput.tsx +20 -0
  138. package/src/ui/Modal/FormModal.spec.tsx +20 -0
  139. package/src/ui/Modal/FormModal.tsx +17 -0
  140. package/src/ui/Modal/Modal.spec.tsx +113 -0
  141. package/src/ui/Modal/Modal.tsx +97 -0
  142. package/src/ui/Modal/_modal.scss +24 -0
  143. package/src/ui/ToolbarButton/ToolbarButton.tsx +26 -0
  144. package/src/ui/ToolbarButton/_toolbar-button.scss +17 -0
  145. package/src/ui/ToolbarDropdown/ToolbarDropdown.spec.tsx +78 -0
  146. package/src/ui/ToolbarDropdown/ToolbarDropdown.tsx +42 -0
  147. package/src/ui/ToolbarDropdown/_toolbar-dropdown.scss +32 -0
  148. package/src/ui/ToolbarDropdownButton/ToolbarDropdownButton.spec.tsx +48 -0
  149. package/src/ui/ToolbarDropdownButton/ToolbarDropdownButton.tsx +29 -0
  150. package/src/ui/ToolbarDropdownButton/_toolbar-dropdown-button.scss +14 -0
  151. package/src/ui/_buttons.scss +19 -0
  152. package/src/ui/_forms.scss +16 -0
  153. package/src/utils/createToolbarPositioner.ts +115 -0
  154. package/src/utils/getCursorRect.ts +5 -0
  155. package/tailwind.config.cjs +83 -0
  156. package/tests/index.ts +2 -0
  157. package/tests/renderWithEditor.tsx +110 -0
  158. package/tests/select.tsx +16 -0
  159. package/tsconfig.json +22 -0
  160. package/vite.config.ts +19 -0
  161. package/lib/EditorToolbar/EditorToolbar.d.ts +0 -7
  162. package/lib/EditorToolbar/EditorToolbar.js +0 -21
@@ -0,0 +1,12 @@
1
+ export type SelectOptions = Record<string, SelectOption>;
2
+ export type SelectOption = {
3
+ label: string;
4
+ };
5
+ export type SelectProps = {
6
+ name: string;
7
+ label?: string;
8
+ value?: string;
9
+ options: SelectOptions;
10
+ onChange?: (value: string) => void;
11
+ };
12
+ export declare const Select: ({ name, label, value, onChange, options }: SelectProps) => JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { Listbox } from '@headlessui/react';
2
+ import React, { useState } from 'react';
3
+ import clsx from 'clsx';
4
+ import ExpandMoreRoundedIcon from '@mui/icons-material/ExpandMoreRounded';
5
+ import ExpandLessRoundedIcon from '@mui/icons-material/ExpandLessRounded';
6
+ export const Select = ({ name, label, value, onChange, options }) => {
7
+ const [selectedOption, setSelectedOptions] = useState(value ? options[value] : null);
8
+ const handleChange = (value) => {
9
+ setSelectedOptions(options[value]);
10
+ onChange?.(value);
11
+ };
12
+ return (React.createElement(Listbox, { value: value, name: name, "aria-labelledby": name, onChange: handleChange }, ({ open }) => (React.createElement(React.Fragment, null,
13
+ label && React.createElement(Listbox.Label, { className: "squiz-fte-form-label" }, label),
14
+ React.createElement("div", { className: "relative text-md text-gray-800" },
15
+ React.createElement(Listbox.Button, { className: "w-full cursor-default rounded border-2 border-gray-300 bg-white py-2 pl-3 pr-10 focus:border-blue-300 focus:outline-none" },
16
+ React.createElement("span", { className: "flex items-center" },
17
+ React.createElement("span", { className: "block truncate" },
18
+ selectedOption?.label,
19
+ "\u00A0")),
20
+ React.createElement("span", { className: "pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2" }, !open ? (React.createElement(ExpandMoreRoundedIcon, { className: "text-gray-500", "aria-hidden": "true" })) : (React.createElement(ExpandLessRoundedIcon, { className: "text-gray-500", "aria-hidden": "true" })))),
21
+ React.createElement(Listbox.Options, { className: "absolute z-10 mt-1 w-full overflow-auto rounded border-2 border-gray-300 bg-white" }, Object.entries(options).map(([key, option]) => (React.createElement(Listbox.Option, { key: key, value: key, className: ({ active }) => clsx(active ? 'bg-gray-100' : 'bg-white', 'relative cursor-default select-none flex hover:bg-gray-100 py-2 px-3') },
22
+ React.createElement("span", { className: "block truncate" }, option.label))))))))));
23
+ };
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ export declare const TextInput: React.ForwardRefExoticComponent<React.InputHTMLAttributes<HTMLInputElement> & {
3
+ label?: string | undefined;
4
+ } & React.RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,7 @@
1
+ import React, { forwardRef } from 'react';
2
+ const TextInputInternal = ({ name, label, ...rest }, ref) => {
3
+ return (React.createElement(React.Fragment, null,
4
+ label && (React.createElement("label", { htmlFor: name, className: "squiz-fte-form-label" }, label)),
5
+ React.createElement("input", { ref: ref, id: name, name: name, type: "text", className: "squiz-fte-form-control", ...rest })));
6
+ };
7
+ export const TextInput = forwardRef(TextInputInternal);
@@ -0,0 +1,5 @@
1
+ import { ReactElement } from 'react';
2
+ import { ModalProps } from './Modal';
3
+ type FormModalProps = Omit<ModalProps, 'onSubmit'>;
4
+ declare const FormModal: (props: FormModalProps) => ReactElement;
5
+ export default FormModal;
@@ -0,0 +1,11 @@
1
+ import React, { createRef } from 'react';
2
+ import Modal from './Modal';
3
+ const FormModal = (props) => {
4
+ const ref = createRef();
5
+ const handleSubmit = () => {
6
+ const form = ref.current?.querySelector('form');
7
+ form?.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
8
+ };
9
+ return React.createElement(Modal, { ref: ref, ...props, onSubmit: handleSubmit });
10
+ };
11
+ export default FormModal;
@@ -0,0 +1,10 @@
1
+ import React, { ReactElement } from 'react';
2
+ export type ModalProps = {
3
+ title: string;
4
+ children: ReactElement;
5
+ onCancel: () => void;
6
+ onSubmit?: () => void;
7
+ className?: string;
8
+ };
9
+ declare const _default: React.ForwardRefExoticComponent<ModalProps & React.RefAttributes<HTMLDivElement>>;
10
+ export default _default;
@@ -0,0 +1,48 @@
1
+ import React, { forwardRef, useEffect, useMemo } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
4
+ import { FocusTrap } from '@mui/base';
5
+ const Modal = ({ children, title, onCancel, onSubmit, className }, ref) => {
6
+ const container = useMemo(() => {
7
+ const element = document.createElement('div');
8
+ element.classList.add('squiz-fte-scope');
9
+ return element;
10
+ }, []);
11
+ const keydown = (e) => {
12
+ if (e.key === 'Escape') {
13
+ onCancel();
14
+ }
15
+ if (e.key === 'Enter') {
16
+ onSubmit && onSubmit();
17
+ }
18
+ };
19
+ // register key listeners for Enter/Escape on key up so the editor doesn't handle the event as well
20
+ useEffect(() => {
21
+ window.addEventListener('keyup', keydown);
22
+ return () => window.removeEventListener('keyup', keydown);
23
+ }, []);
24
+ // add/remove the modal container from the DOM and focus on the first input
25
+ useEffect(() => {
26
+ const firstInput = container.querySelector('input:not([type=hidden])');
27
+ document.body.appendChild(container);
28
+ firstInput?.focus();
29
+ return () => {
30
+ document.body.removeChild(container);
31
+ };
32
+ }, [container]);
33
+ return createPortal(React.createElement(React.Fragment, null,
34
+ React.createElement(FocusTrap, { open: true },
35
+ React.createElement("div", { ref: ref, className: `squiz-fte-modal-wrapper ${className ? className : ''}`, tabIndex: -1 },
36
+ React.createElement("div", { className: "w-modal-sm my-6 mx-auto" },
37
+ React.createElement("div", { className: "squiz-fte-modal" },
38
+ React.createElement("div", { className: "squiz-fte-modal-header p-6 pb-2" },
39
+ React.createElement("h2", { className: "font-semibold text-gray-900 text-heading-2" }, title),
40
+ React.createElement("button", { className: "ml-auto -mr-3 -mt-3 bg-transparent border-0 text-gray-600 font-semibold outline-none focus:outline-none hover:text-color-gray-800", onClick: onCancel, "aria-label": "Close" },
41
+ React.createElement(CloseRoundedIcon, null))),
42
+ React.createElement("div", { className: "squiz-fte-modal-content" }, children),
43
+ React.createElement("div", { className: "squiz-fte-modal-footer p-6 pt-3" },
44
+ React.createElement("button", { className: "squiz-fte-modal-footer__button bg-gray-200 text-gray-700 mr-2 hover:bg-gray-300", type: "button", onClick: onCancel, "aria-label": "Cancel" }, "Cancel"),
45
+ onSubmit && (React.createElement("button", { className: "squiz-fte-modal-footer__button bg-blue-300 text-white hover:bg-blue-400", type: "button", onClick: onSubmit, "aria-label": "Apply" }, "Apply"))))))),
46
+ React.createElement("div", { className: "opacity-25 fixed inset-0 z-40 bg-black" })), container);
47
+ };
48
+ export default forwardRef(Modal);
@@ -1,7 +1,7 @@
1
1
  import { ReactElement } from 'react';
2
2
  type ToolbarButtonProps = {
3
3
  handleOnClick: () => void;
4
- isDisabled: boolean;
4
+ isDisabled?: boolean;
5
5
  isActive: boolean;
6
6
  icon: ReactElement;
7
7
  label: string;
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
2
  const ToolbarButton = ({ handleOnClick, isDisabled, isActive, icon, label }) => {
3
- return (React.createElement("button", { "aria-label": label, title: label, type: "button", onClick: handleOnClick, disabled: isDisabled, className: `btn toolbar-button ${isActive ? 'is-active' : ''}` }, icon));
3
+ return (React.createElement("button", { "aria-label": label, title: label, type: "button", onClick: handleOnClick, disabled: isDisabled, className: `squiz-fte-btn toolbar-button ${isActive ? 'is-active' : ''}` }, icon));
4
4
  };
5
5
  export default ToolbarButton;
@@ -0,0 +1,6 @@
1
+ type ToolbarDropdownProps = {
2
+ children: JSX.Element | JSX.Element[];
3
+ label: string;
4
+ };
5
+ declare const ToolbarDropdown: ({ children, label }: ToolbarDropdownProps) => JSX.Element;
6
+ export default ToolbarDropdown;
@@ -0,0 +1,20 @@
1
+ import React, { useState } from 'react';
2
+ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
3
+ const ToolbarDropdown = ({ children, label }) => {
4
+ const [isOpen, setOpen] = useState(false);
5
+ const handleDropDown = () => {
6
+ setOpen(!isOpen);
7
+ };
8
+ const handleBlur = (event) => {
9
+ if (event.relatedTarget?.id !== 'dropdownMenuButton' && !event.target?.className.includes('is-active')) {
10
+ isOpen && handleDropDown();
11
+ }
12
+ };
13
+ return (React.createElement("div", { className: "toolbar-dropdown", onBlur: handleBlur },
14
+ React.createElement("button", { id: "dropdownHoverButton", className: "toolbar-dropdown__button", onClick: handleDropDown },
15
+ React.createElement("span", { className: "toolbar-dropdown__label" }, label),
16
+ React.createElement(ExpandMoreIcon, { className: "toolbar-dropdown__icon", "aria-hidden": "true" })),
17
+ React.createElement("div", { id: "dropdown", className: `toolbar-dropdown__menu z-10 ${isOpen ? 'block' : 'hidden'} bg-white divide-y w-169` },
18
+ React.createElement("ul", { "aria-labelledby": "dropdownHoverButton", onClick: handleDropDown }, children))));
19
+ };
20
+ export default ToolbarDropdown;
@@ -0,0 +1,9 @@
1
+ type DropdownButtonProps = {
2
+ children?: JSX.Element;
3
+ handleOnClick: () => void;
4
+ isDisabled: boolean;
5
+ isActive: boolean;
6
+ label: string;
7
+ };
8
+ declare const DropdownButton: ({ children, handleOnClick, isDisabled, isActive, label }: DropdownButtonProps) => JSX.Element;
9
+ export default DropdownButton;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import CheckIcon from '@mui/icons-material/Check';
3
+ const DropdownButton = ({ children, handleOnClick, isDisabled, isActive, label }) => {
4
+ return (React.createElement("button", { "aria-label": label, id: "dropdownMenuButton", title: label, type: "button", onClick: handleOnClick, disabled: isDisabled, className: `btn dropdown-button ${isActive ? 'is-active' : ''}` },
5
+ React.createElement("span", null, children || label),
6
+ isActive && React.createElement(CheckIcon, { className: "dropdown-button-icon" })));
7
+ };
8
+ export default DropdownButton;
@@ -0,0 +1,18 @@
1
+ import { Positioner } from 'remirror/extensions';
2
+ import { Coords } from 'remirror';
3
+ export type ToolbarPositionerProps = {
4
+ types: string[];
5
+ };
6
+ export type ToolbarPositionerRange = {
7
+ isSelectionInView: boolean;
8
+ visible: boolean;
9
+ marks: Record<string, {
10
+ isExclusivelyActive: boolean;
11
+ isActive: boolean;
12
+ }>;
13
+ cursor: {
14
+ from: Coords;
15
+ to: Coords;
16
+ };
17
+ };
18
+ export declare const createToolbarPositioner: ({ types }: ToolbarPositionerProps) => Positioner<ToolbarPositionerRange>;
@@ -0,0 +1,81 @@
1
+ import { hasStateChanged, isPositionVisible, Positioner } from 'remirror/extensions';
2
+ import { getMarkRange, getTextSelection } from 'remirror';
3
+ import { getCursorRect } from './getCursorRect';
4
+ /* istanbul ignore next */
5
+ export const createToolbarPositioner = ({ types }) => {
6
+ // Inspired by "createMarkPositioner".
7
+ // See: https://github.com/remirror/remirror/blob/107cba/packages/remirror__extension-positioner/src/core-positioners.ts#L267
8
+ return Positioner.create({
9
+ hasChanged: hasStateChanged,
10
+ getActive: (props) => {
11
+ const { state, view } = props;
12
+ try {
13
+ const selection = getTextSelection(state.selection, state.doc);
14
+ const cursor = { from: view.coordsAtPos(selection.from), to: view.coordsAtPos(selection.to) };
15
+ const data = {
16
+ isSelectionInView: isPositionVisible(getCursorRect(cursor.from), view.dom) ||
17
+ isPositionVisible(getCursorRect(cursor.to), view.dom),
18
+ cursor: cursor,
19
+ visible: false,
20
+ marks: {},
21
+ };
22
+ data.visible = !selection.empty && data.isSelectionInView;
23
+ types.forEach((type) => {
24
+ const markRange = getMarkRange(selection.$from, type, selection.$to);
25
+ if (!markRange) {
26
+ data.marks[type] = { isActive: false, isExclusivelyActive: false };
27
+ return;
28
+ }
29
+ // exclusively active =
30
+ // the entire selection has the mark applied.
31
+ // active =
32
+ // at least part of the selection has the mark applied. "getMarkRanges" will return an empty array
33
+ // if this isn't the case. if there is no selection the cursor must be within the bounds of the mark,
34
+ // not on the edges.
35
+ const isExclusivelyActive = selection.empty
36
+ ? selection.from > markRange.from && selection.to < markRange.to
37
+ : selection.from >= markRange.from && selection.to <= markRange.to;
38
+ const isActive = selection.empty ? selection.from > markRange.from && selection.to < markRange.to : true;
39
+ // the toolbar will be visible if there is either a selection, or we are within the bounds of a mark
40
+ // we have formatting tools for.
41
+ data.visible = data.visible || isExclusivelyActive;
42
+ data.marks[type] = { isExclusivelyActive, isActive };
43
+ });
44
+ return data.visible ? [data] : Positioner.EMPTY;
45
+ }
46
+ catch {
47
+ return Positioner.EMPTY;
48
+ }
49
+ },
50
+ getPosition: (props) => {
51
+ const { element, data, view } = props;
52
+ const { cursor, visible } = data;
53
+ const { from, to } = cursor;
54
+ const parent = element.offsetParent ?? view.dom;
55
+ const parentRect = parent.getBoundingClientRect();
56
+ const height = Math.abs(to.bottom - from.top);
57
+ // Hack to get JSDOM to work, positioning doesn't work great here.
58
+ if (isNaN(parentRect.top)) {
59
+ return {
60
+ rect: new DOMRect(0, 0, 0, 0),
61
+ y: 0,
62
+ x: 0,
63
+ height: 0,
64
+ width: 0,
65
+ visible: false,
66
+ };
67
+ }
68
+ // True when the selection spans multiple lines.
69
+ const spansMultipleLines = height > from.bottom - from.top;
70
+ // The position furthest to the left.
71
+ const leftmost = Math.min(from.left, to.left);
72
+ // The position nearest the top.
73
+ const topmost = Math.min(from.top, to.top);
74
+ const left = parent.scrollLeft + (spansMultipleLines ? to.left - parentRect.left : leftmost - parentRect.left);
75
+ const top = parent.scrollTop + topmost - parentRect.top;
76
+ const width = spansMultipleLines ? 1 : Math.abs(from.left - to.right);
77
+ const rect = new DOMRect(spansMultipleLines ? to.left : leftmost, topmost, width, height);
78
+ return { rect, y: top, x: left, height, width, visible };
79
+ },
80
+ });
81
+ };
@@ -0,0 +1,2 @@
1
+ import { Coords } from 'remirror';
2
+ export declare const getCursorRect: (coords: Coords) => DOMRect;
@@ -0,0 +1,3 @@
1
+ export const getCursorRect = (coords) => {
2
+ return new DOMRect(coords.left, coords.top, 1, coords.top - coords.bottom);
3
+ };
package/package.json CHANGED
@@ -1,43 +1,51 @@
1
1
  {
2
2
  "name": "@squiz/formatted-text-editor",
3
- "version": "1.12.0-alpha.9",
3
+ "version": "1.12.1-alpha.0",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "scripts": {
7
- "dev": "vite",
7
+ "build": "vite build",
8
8
  "clean": "rimraf ./lib",
9
9
  "compile": "npm run clean && npm run compile:styles && npm run compile:code",
10
10
  "compile:code": "tsc",
11
11
  "compile:styles": "node build.js",
12
+ "dev": "vite",
12
13
  "preview": "vite preview",
13
- "build": "vite build",
14
14
  "test": "jest",
15
- "test:watch": "jest --watch",
16
- "test:e2e": "vite build && vite preview --port 8080 & cypress open"
15
+ "test:e2e": "vite build && vite preview --port 8080 & cypress open",
16
+ "test:watch": "jest --watch"
17
17
  },
18
- "files": [
19
- "lib/**"
20
- ],
21
18
  "dependencies": {
19
+ "@headlessui/react": "1.7.11",
22
20
  "@mui/icons-material": "5.11.0",
23
- "@remirror/react": "2.0.25"
21
+ "@remirror/react": "2.0.25",
22
+ "clsx": "1.2.1",
23
+ "react-hook-form": "7.43.2"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@testing-library/cypress": "9.0.0",
27
27
  "@testing-library/jest-dom": "5.16.5",
28
- "@testing-library/react": "12.1.5",
28
+ "@testing-library/react": "14.0.0",
29
+ "@testing-library/user-event": "14.4.3",
29
30
  "@types/react": "18.0.26",
30
31
  "@types/react-dom": "18.0.9",
31
32
  "@vitejs/plugin-react": "3.0.0",
33
+ "autoprefixer": "10.4.13",
32
34
  "cypress": "12.5.1",
33
- "eslint-config-prettier": "8.6.0",
34
35
  "eslint-plugin-cypress": "2.12.1",
35
36
  "eslint-plugin-jsx-a11y": "6.7.1",
36
37
  "eslint-plugin-react": "7.32.2",
38
+ "jest": "29.4.1",
37
39
  "jest-environment-jsdom": "29.4.1",
40
+ "jest-remirror": "2.1.3",
41
+ "postcss": "8.4.21",
42
+ "postcss-nested": "6.0.0",
43
+ "postcss-prefix-selector": "1.16.0",
38
44
  "react": "18.2.0",
39
45
  "react-dom": "18.2.0",
40
46
  "rimraf": "4.1.2",
47
+ "tailwindcss": "3.2.6",
48
+ "ts-jest": "29.0.5",
41
49
  "typescript": "4.9.3",
42
50
  "vite": "4.0.0"
43
51
  },
@@ -57,5 +65,6 @@
57
65
  },
58
66
  "volta": {
59
67
  "node": "16.19.0"
60
- }
61
- }
68
+ },
69
+ "gitHead": "4a0c81e76ca50706d137dee82869e3ea7844376e"
70
+ }
@@ -0,0 +1,12 @@
1
+ module.exports = {
2
+ plugins: [
3
+ require('tailwindcss'),
4
+ require('autoprefixer'),
5
+ require('postcss-nested'),
6
+ require('postcss-prefix-selector')({
7
+ prefix: '.squiz-fte-scope',
8
+ exclude: ['.squiz-fte-scope__floating-popover'],
9
+ includeFiles: ['./src/index.scss'],
10
+ }),
11
+ ],
12
+ };
@@ -0,0 +1,43 @@
1
+ import { Remirror, useRemirror, useRemirrorContext } from '@remirror/react';
2
+ import React from 'react';
3
+ import { Toolbar, FloatingToolbar } from '../EditorToolbar';
4
+ import { Extensions } from '../Extensions/Extensions';
5
+
6
+ type MockEditorProps = {
7
+ setContent?: any;
8
+ };
9
+
10
+ /**
11
+ * @deprecated Use "renderWithEditor" and render the specific component being tested instead.
12
+ */
13
+ export const MockEditor = ({ setContent }: MockEditorProps) => {
14
+ const { manager, state, setState } = useRemirror({
15
+ extensions: Extensions,
16
+ selection: 'start',
17
+ stringHandler: 'html',
18
+ });
19
+
20
+ const Component = () => {
21
+ setContent && setContent.mockImplementation(useRemirrorContext().setContent);
22
+ return null;
23
+ };
24
+
25
+ const handleChange = (parameter: { state: any }) => {
26
+ setState(parameter.state);
27
+ };
28
+
29
+ return (
30
+ <Remirror
31
+ manager={manager}
32
+ onChange={handleChange}
33
+ placeholder="Write something"
34
+ label="Text editor"
35
+ state={state}
36
+ autoRender="start"
37
+ >
38
+ <Toolbar />
39
+ <Component />
40
+ <FloatingToolbar />
41
+ </Remirror>
42
+ );
43
+ };