@crystallize/design-system 1.3.2 → 1.4.1

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 (246) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/TableComponent-I2YOOYOU.css +281 -0
  3. package/dist/TableComponent-QINOO453.mjs +1377 -0
  4. package/dist/arrow-clockwise-Z2G6UEGP.svg +1 -0
  5. package/dist/arrow-counterclockwise-2O5EYVJT.svg +1 -0
  6. package/dist/bg-color-HB2WDYGO.svg +1 -0
  7. package/dist/camera-CR7D2PNH.svg +1 -0
  8. package/dist/caret-right-fill-FFBNEXVX.svg +1 -0
  9. package/dist/chat-square-quote-CI6PUJHH.svg +1 -0
  10. package/dist/chevron-down-3FRWSIKS.svg +1 -0
  11. package/dist/chunk-VUXQZRSP.mjs +737 -0
  12. package/dist/clipboard-OSEFDF25.svg +1 -0
  13. package/dist/close-FH57ZMJF.svg +1 -0
  14. package/dist/code-SEVR6TIQ.svg +1 -0
  15. package/dist/copy-DMGDODUL.svg +1 -0
  16. package/dist/diagram-2-CEJUD2B4.svg +1 -0
  17. package/dist/download-JXUGIUCX.svg +1 -0
  18. package/dist/draggable-block-menu-KKHDNKJA.svg +1 -0
  19. package/dist/dropdown-more-BHZ5COKX.svg +1 -0
  20. package/dist/file-image-TIQPFJX4.svg +1 -0
  21. package/dist/filetype-gif-OG2BEYYK.svg +1 -0
  22. package/dist/font-color-J4GA3ZJO.svg +1 -0
  23. package/dist/font-family-ZU5N6TTE.svg +1 -0
  24. package/dist/gear-ICMT4NTP.svg +1 -0
  25. package/dist/horizontal-rule-N6RD2V7H.svg +1 -0
  26. package/dist/indent-MJ6JIMCK.svg +1 -0
  27. package/dist/index.css +2711 -315
  28. package/dist/index.d.ts +145 -40
  29. package/dist/index.js +10376 -1481
  30. package/dist/index.mjs +7609 -746
  31. package/dist/journal-code-XUT44HDV.svg +1 -0
  32. package/dist/justify-J7X5JEEX.svg +1 -0
  33. package/dist/link-W52N4JKZ.svg +1 -0
  34. package/dist/list-ol-2ZEUN4Z7.svg +1 -0
  35. package/dist/list-ul-DVKNUP47.svg +1 -0
  36. package/dist/lock-WCYOZOHW.svg +1 -0
  37. package/dist/lock-fill-JZSKOSHK.svg +1 -0
  38. package/dist/markdown-4BGQNLLT.svg +1 -0
  39. package/dist/mic-H5FNOMM7.svg +1 -0
  40. package/dist/outdent-2LUMUMIP.svg +1 -0
  41. package/dist/paint-bucket-VCISMZTH.svg +1 -0
  42. package/dist/palette-SWGFPRWZ.svg +1 -0
  43. package/dist/pencil-fill-STFSC26F.svg +1 -0
  44. package/dist/plug-HGGGEVS3.svg +1 -0
  45. package/dist/plug-fill-OTG3U4TN.svg +1 -0
  46. package/dist/plus-CQISIKEC.svg +1 -0
  47. package/dist/plus-slash-minus-N22JU4TI.svg +1 -0
  48. package/dist/prettier-WUJ7B5NV.svg +1 -0
  49. package/dist/prettier-error-DYJSLYDP.svg +1 -0
  50. package/dist/square-check-UTG6FU6D.svg +1 -0
  51. package/dist/success-YVXUMPEZ.svg +1 -0
  52. package/dist/table-BR6DI4ZQ.svg +1 -0
  53. package/dist/text-center-UQI6PAEF.svg +1 -0
  54. package/dist/text-left-KT2B6TR3.svg +1 -0
  55. package/dist/text-paragraph-MFTUIIQG.svg +1 -0
  56. package/dist/text-right-SKELPISG.svg +1 -0
  57. package/dist/trash-UOM6D7TD.svg +1 -0
  58. package/dist/type-bold-PY7COC3N.svg +1 -0
  59. package/dist/type-h1-6KJP7YOM.svg +1 -0
  60. package/dist/type-h2-VHI2USC3.svg +1 -0
  61. package/dist/type-h3-JIU77CHO.svg +1 -0
  62. package/dist/type-h4-P5EHKDAL.svg +1 -0
  63. package/dist/type-h5-CS2KYVRG.svg +1 -0
  64. package/dist/type-h6-J2O74LJZ.svg +1 -0
  65. package/dist/type-italic-3DSFOSG2.svg +1 -0
  66. package/dist/type-strikethrough-E2KKQFSX.svg +1 -0
  67. package/dist/type-subscript-BMPTRIBU.svg +1 -0
  68. package/dist/type-superscript-EDF6EPAA.svg +1 -0
  69. package/dist/type-underline-CBFA5VLF.svg +1 -0
  70. package/dist/upload-Q6KICGZW.svg +1 -0
  71. package/dist/user-EOI2NEFZ.svg +1 -0
  72. package/package.json +30 -6
  73. package/src/dialog/dialog.tsx +1 -0
  74. package/src/icon-button/icon-button.css +16 -14
  75. package/src/index.ts +4 -4
  76. package/src/input/input.css +1 -1
  77. package/src/input-with-label/input-with-label.css +1 -1
  78. package/src/rich-text-editor/appSettings.ts +28 -0
  79. package/src/rich-text-editor/context/SettingsContext.tsx +71 -0
  80. package/src/rich-text-editor/context/SharedAutocompleteContext.tsx +60 -0
  81. package/src/rich-text-editor/context/SharedHistoryContext.tsx +25 -0
  82. package/src/rich-text-editor/hooks/useReport.ts +64 -0
  83. package/src/rich-text-editor/images/cat-typing.gif +0 -0
  84. package/src/rich-text-editor/images/emoji/1F600.png +0 -0
  85. package/src/rich-text-editor/images/emoji/1F641.png +0 -0
  86. package/src/rich-text-editor/images/emoji/1F642.png +0 -0
  87. package/src/rich-text-editor/images/emoji/2764.png +0 -0
  88. package/src/rich-text-editor/images/emoji/LICENSE.md +5 -0
  89. package/src/rich-text-editor/images/icons/LICENSE.md +5 -0
  90. package/src/rich-text-editor/images/icons/arrow-clockwise.svg +1 -0
  91. package/src/rich-text-editor/images/icons/arrow-counterclockwise.svg +1 -0
  92. package/src/rich-text-editor/images/icons/bg-color.svg +1 -0
  93. package/src/rich-text-editor/images/icons/camera.svg +1 -0
  94. package/src/rich-text-editor/images/icons/card-checklist.svg +1 -0
  95. package/src/rich-text-editor/images/icons/caret-right-fill.svg +1 -0
  96. package/src/rich-text-editor/images/icons/chat-left-text.svg +1 -0
  97. package/src/rich-text-editor/images/icons/chat-right-dots.svg +1 -0
  98. package/src/rich-text-editor/images/icons/chat-right-text.svg +1 -0
  99. package/src/rich-text-editor/images/icons/chat-right.svg +1 -0
  100. package/src/rich-text-editor/images/icons/chat-square-quote.svg +1 -0
  101. package/src/rich-text-editor/images/icons/chevron-down.svg +1 -0
  102. package/src/rich-text-editor/images/icons/clipboard.svg +1 -0
  103. package/src/rich-text-editor/images/icons/close.svg +1 -0
  104. package/src/rich-text-editor/images/icons/code.svg +1 -0
  105. package/src/rich-text-editor/images/icons/comments.svg +1 -0
  106. package/src/rich-text-editor/images/icons/copy.svg +1 -0
  107. package/src/rich-text-editor/images/icons/diagram-2.svg +1 -0
  108. package/src/rich-text-editor/images/icons/download.svg +1 -0
  109. package/src/rich-text-editor/images/icons/draggable-block-menu.svg +1 -0
  110. package/src/rich-text-editor/images/icons/dropdown-more.svg +1 -0
  111. package/src/rich-text-editor/images/icons/figma.svg +1 -0
  112. package/src/rich-text-editor/images/icons/file-image.svg +1 -0
  113. package/src/rich-text-editor/images/icons/filetype-gif.svg +1 -0
  114. package/src/rich-text-editor/images/icons/font-color.svg +1 -0
  115. package/src/rich-text-editor/images/icons/font-family.svg +1 -0
  116. package/src/rich-text-editor/images/icons/gear.svg +1 -0
  117. package/src/rich-text-editor/images/icons/horizontal-rule.svg +1 -0
  118. package/src/rich-text-editor/images/icons/indent.svg +1 -0
  119. package/src/rich-text-editor/images/icons/journal-code.svg +1 -0
  120. package/src/rich-text-editor/images/icons/journal-text.svg +1 -0
  121. package/src/rich-text-editor/images/icons/justify.svg +1 -0
  122. package/src/rich-text-editor/images/icons/link.svg +1 -0
  123. package/src/rich-text-editor/images/icons/list-ol.svg +1 -0
  124. package/src/rich-text-editor/images/icons/list-ul.svg +1 -0
  125. package/src/rich-text-editor/images/icons/lock-fill.svg +1 -0
  126. package/src/rich-text-editor/images/icons/lock.svg +1 -0
  127. package/src/rich-text-editor/images/icons/markdown.svg +1 -0
  128. package/src/rich-text-editor/images/icons/mic.svg +1 -0
  129. package/src/rich-text-editor/images/icons/outdent.svg +1 -0
  130. package/src/rich-text-editor/images/icons/paint-bucket.svg +1 -0
  131. package/src/rich-text-editor/images/icons/palette.svg +1 -0
  132. package/src/rich-text-editor/images/icons/pencil-fill.svg +1 -0
  133. package/src/rich-text-editor/images/icons/plug-fill.svg +1 -0
  134. package/src/rich-text-editor/images/icons/plug.svg +1 -0
  135. package/src/rich-text-editor/images/icons/plus-slash-minus.svg +1 -0
  136. package/src/rich-text-editor/images/icons/plus.svg +1 -0
  137. package/src/rich-text-editor/images/icons/prettier-error.svg +1 -0
  138. package/src/rich-text-editor/images/icons/prettier.svg +1 -0
  139. package/src/rich-text-editor/images/icons/send.svg +1 -0
  140. package/src/rich-text-editor/images/icons/square-check.svg +1 -0
  141. package/src/rich-text-editor/images/icons/sticky.svg +1 -0
  142. package/src/rich-text-editor/images/icons/success.svg +1 -0
  143. package/src/rich-text-editor/images/icons/table.svg +1 -0
  144. package/src/rich-text-editor/images/icons/text-center.svg +1 -0
  145. package/src/rich-text-editor/images/icons/text-left.svg +1 -0
  146. package/src/rich-text-editor/images/icons/text-paragraph.svg +1 -0
  147. package/src/rich-text-editor/images/icons/text-right.svg +1 -0
  148. package/src/rich-text-editor/images/icons/trash.svg +1 -0
  149. package/src/rich-text-editor/images/icons/trash3.svg +1 -0
  150. package/src/rich-text-editor/images/icons/tweet.svg +1 -0
  151. package/src/rich-text-editor/images/icons/type-bold.svg +1 -0
  152. package/src/rich-text-editor/images/icons/type-h1.svg +1 -0
  153. package/src/rich-text-editor/images/icons/type-h2.svg +1 -0
  154. package/src/rich-text-editor/images/icons/type-h3.svg +1 -0
  155. package/src/rich-text-editor/images/icons/type-h4.svg +1 -0
  156. package/src/rich-text-editor/images/icons/type-h5.svg +1 -0
  157. package/src/rich-text-editor/images/icons/type-h6.svg +1 -0
  158. package/src/rich-text-editor/images/icons/type-italic.svg +1 -0
  159. package/src/rich-text-editor/images/icons/type-strikethrough.svg +1 -0
  160. package/src/rich-text-editor/images/icons/type-subscript.svg +1 -0
  161. package/src/rich-text-editor/images/icons/type-superscript.svg +1 -0
  162. package/src/rich-text-editor/images/icons/type-underline.svg +1 -0
  163. package/src/rich-text-editor/images/icons/upload.svg +1 -0
  164. package/src/rich-text-editor/images/icons/user.svg +1 -0
  165. package/src/rich-text-editor/images/icons/youtube.svg +1 -0
  166. package/src/rich-text-editor/images/image/LICENSE.md +5 -0
  167. package/src/rich-text-editor/images/landscape.jpg +0 -0
  168. package/src/rich-text-editor/images/logo.svg +1 -0
  169. package/src/rich-text-editor/images/yellow-flower-small.jpg +0 -0
  170. package/src/rich-text-editor/images/yellow-flower.jpg +0 -0
  171. package/src/rich-text-editor/index.ts +1 -0
  172. package/src/rich-text-editor/model/crystallize-rich-text-types/code.ts +39 -0
  173. package/src/rich-text-editor/model/crystallize-rich-text-types/headings.ts +12 -0
  174. package/src/rich-text-editor/model/crystallize-rich-text-types/index.ts +69 -0
  175. package/src/rich-text-editor/model/crystallize-rich-text-types/link.ts +9 -0
  176. package/src/rich-text-editor/model/crystallize-rich-text-types/table.ts +16 -0
  177. package/src/rich-text-editor/model/crystallize-to-lexical.ts +186 -0
  178. package/src/rich-text-editor/model/lexical-to-crystallize.ts +232 -0
  179. package/src/rich-text-editor/nodes/AutocompleteNode.tsx +96 -0
  180. package/src/rich-text-editor/nodes/BaseNodes.ts +45 -0
  181. package/src/rich-text-editor/nodes/KeywordNode.ts +73 -0
  182. package/src/rich-text-editor/nodes/TableCellNodes.ts +31 -0
  183. package/src/rich-text-editor/nodes/TableComponent.tsx +1547 -0
  184. package/src/rich-text-editor/nodes/TableNode.tsx +398 -0
  185. package/src/rich-text-editor/plugins/ActionsPlugin/index.tsx +83 -0
  186. package/src/rich-text-editor/plugins/AutoLinkPlugin/index.tsx +47 -0
  187. package/src/rich-text-editor/plugins/AutocompletePlugin/index.tsx +2536 -0
  188. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/components/CopyButton/index.tsx +60 -0
  189. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/components/PrettierButton/index.css +14 -0
  190. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx +140 -0
  191. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/index.css +46 -0
  192. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/index.tsx +155 -0
  193. package/src/rich-text-editor/plugins/CodeHighlightPlugin/index.ts +21 -0
  194. package/src/rich-text-editor/plugins/ComponentPickerPlugin/index.tsx +320 -0
  195. package/src/rich-text-editor/plugins/DragDropPastePlugin/index.ts +40 -0
  196. package/src/rich-text-editor/plugins/DraggableBlockPlugin/index.css +36 -0
  197. package/src/rich-text-editor/plugins/DraggableBlockPlugin/index.tsx +368 -0
  198. package/src/rich-text-editor/plugins/FloatingLinkEditorPlugin/index.css +40 -0
  199. package/src/rich-text-editor/plugins/FloatingLinkEditorPlugin/index.tsx +305 -0
  200. package/src/rich-text-editor/plugins/FloatingTextFormatToolbarPlugin/index.css +128 -0
  201. package/src/rich-text-editor/plugins/FloatingTextFormatToolbarPlugin/index.tsx +351 -0
  202. package/src/rich-text-editor/plugins/LinkPlugin/index.tsx +16 -0
  203. package/src/rich-text-editor/plugins/ListMaxIndentLevelPlugin/index.ts +86 -0
  204. package/src/rich-text-editor/plugins/MarkdownShortcutPlugin/index.tsx +16 -0
  205. package/src/rich-text-editor/plugins/MarkdownTransformers/index.ts +195 -0
  206. package/src/rich-text-editor/plugins/MaxLengthPlugin/index.tsx +49 -0
  207. package/src/rich-text-editor/plugins/SpeechToTextPlugin/index.ts +113 -0
  208. package/src/rich-text-editor/plugins/TabFocusPlugin/index.tsx +65 -0
  209. package/src/rich-text-editor/plugins/TableActionMenuPlugin/index.tsx +481 -0
  210. package/src/rich-text-editor/plugins/TableCellResizer/index.css +12 -0
  211. package/src/rich-text-editor/plugins/TableCellResizer/index.tsx +386 -0
  212. package/src/rich-text-editor/plugins/TablePlugin.tsx +190 -0
  213. package/src/rich-text-editor/plugins/ToolbarPlugin/index.tsx +726 -0
  214. package/src/rich-text-editor/plugins/TreeViewPlugin/index.tsx +25 -0
  215. package/src/rich-text-editor/plugins/TypingPerfPlugin/index.ts +117 -0
  216. package/src/rich-text-editor/rich-text-editor.css +1396 -0
  217. package/src/rich-text-editor/rich-text-editor.stories.tsx +385 -0
  218. package/src/rich-text-editor/rich-text-editor.tsx +228 -0
  219. package/src/rich-text-editor/tests/rich-text-editor-basic-rendering.test.tsx +47 -0
  220. package/src/rich-text-editor/tests/rich-text-editor-code.test.tsx +39 -0
  221. package/src/rich-text-editor/tests/rich-text-editor-model-basics.test.tsx +56 -0
  222. package/src/rich-text-editor/tests/rich-text-editor-model-conversions.test.tsx +195 -0
  223. package/src/rich-text-editor/tests/rich-text-editor-onchange.test.tsx +37 -0
  224. package/src/rich-text-editor/tests/rich-text-editor-quote.test.tsx +36 -0
  225. package/src/rich-text-editor/tests/rich-text-editor-text-formats.test.tsx +135 -0
  226. package/src/rich-text-editor/tests/rich-text-editor-typing.test.tsx +73 -0
  227. package/src/rich-text-editor/tests/utils.ts +23 -0
  228. package/src/rich-text-editor/themes/PlaygroundEditorTheme.css +433 -0
  229. package/src/rich-text-editor/themes/PlaygroundEditorTheme.ts +113 -0
  230. package/src/rich-text-editor/types.ts +5 -0
  231. package/src/rich-text-editor/ui/ContentEditable.css +13 -0
  232. package/src/rich-text-editor/ui/ContentEditable.tsx +15 -0
  233. package/src/rich-text-editor/ui/LinkPreview.css +57 -0
  234. package/src/rich-text-editor/ui/LinkPreview.tsx +169 -0
  235. package/src/rich-text-editor/utils/environment.ts +1 -0
  236. package/src/rich-text-editor/utils/getDOMRangeRect.ts +42 -0
  237. package/src/rich-text-editor/utils/getSelectedNode.ts +27 -0
  238. package/src/rich-text-editor/utils/guard.ts +10 -0
  239. package/src/rich-text-editor/utils/isMobileWidth.ts +7 -0
  240. package/src/rich-text-editor/utils/joinClasses.ts +13 -0
  241. package/src/rich-text-editor/utils/point.ts +55 -0
  242. package/src/rich-text-editor/utils/rect.ts +158 -0
  243. package/src/rich-text-editor/utils/setFloatingElemPosition.ts +46 -0
  244. package/src/rich-text-editor/utils/swipe.ts +127 -0
  245. package/src/rich-text-editor/utils/url.ts +33 -0
  246. package/src/Tokens.stories.tsx +0 -18
@@ -0,0 +1,386 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import type {Cell} from '@lexical/table';
9
+ import type {LexicalEditor} from 'lexical';
10
+
11
+ import './index.css';
12
+
13
+ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
14
+ import useLexicalEditable from '@lexical/react/useLexicalEditable';
15
+ import {
16
+ $getTableColumnIndexFromTableCellNode,
17
+ $getTableNodeFromLexicalNodeOrThrow,
18
+ $getTableRowIndexFromTableCellNode,
19
+ $isTableCellNode,
20
+ $isTableRowNode,
21
+ getCellFromTarget,
22
+ } from '@lexical/table';
23
+ import {
24
+ $getNearestNodeFromDOMNode,
25
+ $getSelection,
26
+ COMMAND_PRIORITY_HIGH,
27
+ DEPRECATED_$isGridSelection,
28
+ SELECTION_CHANGE_COMMAND,
29
+ } from 'lexical';
30
+ import * as React from 'react';
31
+ import {
32
+ MouseEventHandler,
33
+ ReactPortal,
34
+ useCallback,
35
+ useEffect,
36
+ useMemo,
37
+ useRef,
38
+ useState,
39
+ } from 'react';
40
+ import {createPortal} from 'react-dom';
41
+
42
+ type MousePosition = {
43
+ x: number;
44
+ y: number;
45
+ };
46
+
47
+ type MouseDraggingDirection = 'right' | 'bottom';
48
+
49
+ const MIN_ROW_HEIGHT = 33;
50
+ const MIN_COLUMN_WIDTH = 50;
51
+
52
+ function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element {
53
+ const targetRef = useRef<HTMLElement | null>(null);
54
+ const resizerRef = useRef<HTMLDivElement | null>(null);
55
+ const tableRectRef = useRef<ClientRect | null>(null);
56
+
57
+ const mouseStartPosRef = useRef<MousePosition | null>(null);
58
+ const [mouseCurrentPos, updateMouseCurrentPos] =
59
+ useState<MousePosition | null>(null);
60
+
61
+ const [activeCell, updateActiveCell] = useState<Cell | null>(null);
62
+ const [isSelectingGrid, updateIsSelectingGrid] = useState<boolean>(false);
63
+ const [draggingDirection, updateDraggingDirection] =
64
+ useState<MouseDraggingDirection | null>(null);
65
+
66
+ useEffect(() => {
67
+ return editor.registerCommand(
68
+ SELECTION_CHANGE_COMMAND,
69
+ (payload) => {
70
+ const selection = $getSelection();
71
+ const isGridSelection = DEPRECATED_$isGridSelection(selection);
72
+
73
+ if (isSelectingGrid !== isGridSelection) {
74
+ updateIsSelectingGrid(isGridSelection);
75
+ }
76
+
77
+ return false;
78
+ },
79
+ COMMAND_PRIORITY_HIGH,
80
+ );
81
+ });
82
+
83
+ const resetState = useCallback(() => {
84
+ updateActiveCell(null);
85
+ targetRef.current = null;
86
+ updateDraggingDirection(null);
87
+ mouseStartPosRef.current = null;
88
+ tableRectRef.current = null;
89
+ }, []);
90
+
91
+ useEffect(() => {
92
+ const onMouseMove = (event: MouseEvent) => {
93
+ setTimeout(() => {
94
+ const target = event.target;
95
+
96
+ if (draggingDirection) {
97
+ updateMouseCurrentPos({
98
+ x: event.clientX,
99
+ y: event.clientY,
100
+ });
101
+ return;
102
+ }
103
+
104
+ if (resizerRef.current && resizerRef.current.contains(target as Node)) {
105
+ return;
106
+ }
107
+
108
+ if (targetRef.current !== target) {
109
+ targetRef.current = target as HTMLElement;
110
+ const cell = getCellFromTarget(target as HTMLElement);
111
+
112
+ if (cell && activeCell !== cell) {
113
+ editor.update(() => {
114
+ const tableCellNode = $getNearestNodeFromDOMNode(cell.elem);
115
+ if (!tableCellNode) {
116
+ throw new Error('TableCellResizer: Table cell node not found.');
117
+ }
118
+
119
+ const tableNode =
120
+ $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
121
+ const tableElement = editor.getElementByKey(tableNode.getKey());
122
+
123
+ if (!tableElement) {
124
+ throw new Error('TableCellResizer: Table element not found.');
125
+ }
126
+
127
+ targetRef.current = target as HTMLElement;
128
+ tableRectRef.current = tableElement.getBoundingClientRect();
129
+ updateActiveCell(cell);
130
+ });
131
+ } else if (cell == null) {
132
+ resetState();
133
+ }
134
+ }
135
+ }, 0);
136
+ };
137
+
138
+ document.addEventListener('mousemove', onMouseMove);
139
+
140
+ return () => {
141
+ document.removeEventListener('mousemove', onMouseMove);
142
+ };
143
+ }, [activeCell, draggingDirection, editor, resetState]);
144
+
145
+ const isHeightChanging = (direction: MouseDraggingDirection) => {
146
+ if (direction === 'bottom') return true;
147
+ return false;
148
+ };
149
+
150
+ const updateRowHeight = useCallback(
151
+ (newHeight: number) => {
152
+ if (!activeCell) {
153
+ throw new Error('TableCellResizer: Expected active cell.');
154
+ }
155
+
156
+ editor.update(() => {
157
+ const tableCellNode = $getNearestNodeFromDOMNode(activeCell.elem);
158
+ if (!$isTableCellNode(tableCellNode)) {
159
+ throw new Error('TableCellResizer: Table cell node not found.');
160
+ }
161
+
162
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
163
+
164
+ const tableRowIndex = $getTableRowIndexFromTableCellNode(tableCellNode);
165
+
166
+ const tableRows = tableNode.getChildren();
167
+
168
+ if (tableRowIndex >= tableRows.length || tableRowIndex < 0) {
169
+ throw new Error('Expected table cell to be inside of table row.');
170
+ }
171
+
172
+ const tableRow = tableRows[tableRowIndex];
173
+
174
+ if (!$isTableRowNode(tableRow)) {
175
+ throw new Error('Expected table row');
176
+ }
177
+
178
+ tableRow.setHeight(newHeight);
179
+ });
180
+ },
181
+ [activeCell, editor],
182
+ );
183
+
184
+ const updateColumnWidth = useCallback(
185
+ (newWidth: number) => {
186
+ if (!activeCell) {
187
+ throw new Error('TableCellResizer: Expected active cell.');
188
+ }
189
+ editor.update(() => {
190
+ const tableCellNode = $getNearestNodeFromDOMNode(activeCell.elem);
191
+ if (!$isTableCellNode(tableCellNode)) {
192
+ throw new Error('TableCellResizer: Table cell node not found.');
193
+ }
194
+
195
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
196
+
197
+ const tableColumnIndex =
198
+ $getTableColumnIndexFromTableCellNode(tableCellNode);
199
+
200
+ const tableRows = tableNode.getChildren();
201
+
202
+ for (let r = 0; r < tableRows.length; r++) {
203
+ const tableRow = tableRows[r];
204
+
205
+ if (!$isTableRowNode(tableRow)) {
206
+ throw new Error('Expected table row');
207
+ }
208
+
209
+ const tableCells = tableRow.getChildren();
210
+
211
+ if (tableColumnIndex >= tableCells.length || tableColumnIndex < 0) {
212
+ throw new Error('Expected table cell to be inside of table row.');
213
+ }
214
+
215
+ const tableCell = tableCells[tableColumnIndex];
216
+
217
+ if (!$isTableCellNode(tableCell)) {
218
+ throw new Error('Expected table cell');
219
+ }
220
+
221
+ tableCell.setWidth(newWidth);
222
+ }
223
+ });
224
+ },
225
+ [activeCell, editor],
226
+ );
227
+
228
+ const toggleResize = useCallback(
229
+ (direction: MouseDraggingDirection): MouseEventHandler<HTMLDivElement> =>
230
+ (event) => {
231
+ event.preventDefault();
232
+ event.stopPropagation();
233
+
234
+ if (!activeCell) {
235
+ throw new Error('TableCellResizer: Expected active cell.');
236
+ }
237
+
238
+ if (draggingDirection === direction && mouseStartPosRef.current) {
239
+ const {x, y} = mouseStartPosRef.current;
240
+
241
+ if (activeCell === null) {
242
+ return;
243
+ }
244
+
245
+ const {height, width} = activeCell.elem.getBoundingClientRect();
246
+
247
+ if (isHeightChanging(direction)) {
248
+ const heightChange = Math.abs(event.clientY - y);
249
+
250
+ const isShrinking = direction === 'bottom' && y > event.clientY;
251
+
252
+ updateRowHeight(
253
+ Math.max(
254
+ isShrinking ? height - heightChange : heightChange + height,
255
+ MIN_ROW_HEIGHT,
256
+ ),
257
+ );
258
+ } else {
259
+ const widthChange = Math.abs(event.clientX - x);
260
+
261
+ const isShrinking = direction === 'right' && x > event.clientX;
262
+
263
+ updateColumnWidth(
264
+ Math.max(
265
+ isShrinking ? width - widthChange : widthChange + width,
266
+ MIN_COLUMN_WIDTH,
267
+ ),
268
+ );
269
+ }
270
+
271
+ resetState();
272
+ } else {
273
+ mouseStartPosRef.current = {
274
+ x: event.clientX,
275
+ y: event.clientY,
276
+ };
277
+ updateMouseCurrentPos(mouseStartPosRef.current);
278
+ updateDraggingDirection(direction);
279
+ }
280
+ },
281
+ [
282
+ activeCell,
283
+ draggingDirection,
284
+ resetState,
285
+ updateColumnWidth,
286
+ updateRowHeight,
287
+ ],
288
+ );
289
+
290
+ const getResizers = useCallback(() => {
291
+ if (activeCell) {
292
+ const {height, width, top, left} =
293
+ activeCell.elem.getBoundingClientRect();
294
+
295
+ const styles = {
296
+ bottom: {
297
+ backgroundColor: 'none',
298
+ cursor: 'row-resize',
299
+ height: '10px',
300
+ left: `${window.pageXOffset + left}px`,
301
+ top: `${window.pageYOffset + top + height}px`,
302
+ width: `${width}px`,
303
+ },
304
+ right: {
305
+ backgroundColor: 'none',
306
+ cursor: 'col-resize',
307
+ height: `${height}px`,
308
+ left: `${window.pageXOffset + left + width}px`,
309
+ top: `${window.pageYOffset + top}px`,
310
+ width: '10px',
311
+ },
312
+ };
313
+
314
+ const tableRect = tableRectRef.current;
315
+
316
+ if (draggingDirection && mouseCurrentPos && tableRect) {
317
+ if (isHeightChanging(draggingDirection)) {
318
+ styles[draggingDirection].left = `${
319
+ window.pageXOffset + tableRect.left
320
+ }px`;
321
+ styles[draggingDirection].top = `${
322
+ window.pageYOffset + mouseCurrentPos.y
323
+ }px`;
324
+ styles[draggingDirection].height = '3px';
325
+ styles[draggingDirection].width = `${tableRect.width}px`;
326
+ } else {
327
+ styles[draggingDirection].top = `${
328
+ window.pageYOffset + tableRect.top
329
+ }px`;
330
+ styles[draggingDirection].left = `${
331
+ window.pageXOffset + mouseCurrentPos.x
332
+ }px`;
333
+ styles[draggingDirection].width = '3px';
334
+ styles[draggingDirection].height = `${tableRect.height}px`;
335
+ }
336
+
337
+ styles[draggingDirection].backgroundColor = '#adf';
338
+ }
339
+
340
+ return styles;
341
+ }
342
+
343
+ return {
344
+ bottom: null,
345
+ left: null,
346
+ right: null,
347
+ top: null,
348
+ };
349
+ }, [activeCell, draggingDirection, mouseCurrentPos]);
350
+
351
+ const resizerStyles = getResizers();
352
+
353
+ return (
354
+ <div ref={resizerRef}>
355
+ {activeCell != null && !isSelectingGrid && (
356
+ <>
357
+ <div
358
+ className="TableCellResizer__resizer TableCellResizer__ui"
359
+ style={resizerStyles.right || undefined}
360
+ onMouseDown={toggleResize('right')}
361
+ onMouseUp={toggleResize('right')}
362
+ />
363
+ <div
364
+ className="TableCellResizer__resizer TableCellResizer__ui"
365
+ style={resizerStyles.bottom || undefined}
366
+ onMouseDown={toggleResize('bottom')}
367
+ onMouseUp={toggleResize('bottom')}
368
+ />
369
+ </>
370
+ )}
371
+ </div>
372
+ );
373
+ }
374
+
375
+ export default function TableCellResizerPlugin(): null | ReactPortal {
376
+ const [editor] = useLexicalComposerContext();
377
+ const isEditable = useLexicalEditable();
378
+
379
+ return useMemo(
380
+ () =>
381
+ isEditable
382
+ ? createPortal(<TableCellResizer editor={editor} />, document.body)
383
+ : null,
384
+ [editor, isEditable],
385
+ );
386
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import { createContext, useContext, useEffect, useMemo, useState } from 'react';
10
+ import * as React from 'react';
11
+ import {
12
+ $createNodeSelection,
13
+ $createParagraphNode,
14
+ $getSelection,
15
+ $isRangeSelection,
16
+ $isRootOrShadowRoot,
17
+ $setSelection,
18
+ COMMAND_PRIORITY_EDITOR,
19
+ createCommand,
20
+ EditorThemeClasses,
21
+ Klass,
22
+ LexicalCommand,
23
+ LexicalEditor,
24
+ LexicalNode,
25
+ } from 'lexical';
26
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
27
+
28
+ import { Button } from '../../button';
29
+ import { InputWithLabel } from '../../input-with-label';
30
+ import { $createTableNodeWithDimensions, TableNode } from '../nodes/TableNode';
31
+
32
+ export type InsertTableCommandPayload = Readonly<{
33
+ columns: string;
34
+ rows: string;
35
+ includeHeaders?: boolean;
36
+ }>;
37
+
38
+ export type CellContextShape = {
39
+ cellEditorConfig: null | CellEditorConfig;
40
+ cellEditorPlugins: null | JSX.Element | Array<JSX.Element>;
41
+ set: (cellEditorConfig: null | CellEditorConfig, cellEditorPlugins: null | JSX.Element | Array<JSX.Element>) => void;
42
+ };
43
+
44
+ export type CellEditorConfig = Readonly<{
45
+ namespace: string;
46
+ nodes?: ReadonlyArray<Klass<LexicalNode>>;
47
+ onError: (error: Error, editor: LexicalEditor) => void;
48
+ readOnly?: boolean;
49
+ theme?: EditorThemeClasses;
50
+ }>;
51
+
52
+ export const INSERT_NEW_TABLE_COMMAND: LexicalCommand<InsertTableCommandPayload> =
53
+ createCommand('INSERT_NEW_TABLE_COMMAND');
54
+
55
+ export const CellContext = createContext<CellContextShape>({
56
+ cellEditorConfig: null,
57
+ cellEditorPlugins: null,
58
+ set: () => {
59
+ // Empty
60
+ },
61
+ });
62
+
63
+ export function TableContext({ children }: { children: JSX.Element }) {
64
+ const [contextValue, setContextValue] = useState<{
65
+ cellEditorConfig: null | CellEditorConfig;
66
+ cellEditorPlugins: null | JSX.Element | Array<JSX.Element>;
67
+ }>({
68
+ cellEditorConfig: null,
69
+ cellEditorPlugins: null,
70
+ });
71
+ return (
72
+ <CellContext.Provider
73
+ value={useMemo(
74
+ () => ({
75
+ cellEditorConfig: contextValue.cellEditorConfig,
76
+ cellEditorPlugins: contextValue.cellEditorPlugins,
77
+ set: (cellEditorConfig, cellEditorPlugins) => {
78
+ setContextValue({ cellEditorConfig, cellEditorPlugins });
79
+ },
80
+ }),
81
+ [contextValue.cellEditorConfig, contextValue.cellEditorPlugins],
82
+ )}
83
+ >
84
+ {children}
85
+ </CellContext.Provider>
86
+ );
87
+ }
88
+
89
+ export function InsertNewTableDialog({ activeEditor }: { activeEditor: LexicalEditor }): JSX.Element {
90
+ const [rows, setRows] = useState('5');
91
+ const [columns, setColumns] = useState('5');
92
+
93
+ const onClick = () => {
94
+ if (parseInt(rows) < 1 || parseInt(columns) < 1) {
95
+ return;
96
+ }
97
+ activeEditor.dispatchCommand(INSERT_NEW_TABLE_COMMAND, { columns, rows });
98
+ };
99
+
100
+ return (
101
+ <>
102
+ <div className="grid grid-cols-[1fr_1px_1fr] border border-gray-100-800 border-solid shadow-sm rounded-md ">
103
+ <InputWithLabel
104
+ label="Rows"
105
+ value={rows}
106
+ placeholder="0"
107
+ type="text"
108
+ inputMode="numeric"
109
+ onChange={e => setRows(e.target.value)}
110
+ />
111
+ <span className="h-full bg-gray-100-800" />
112
+ <InputWithLabel
113
+ type="text"
114
+ label="Columns"
115
+ placeholder="0"
116
+ value={columns}
117
+ inputMode="numeric"
118
+ onChange={e => setColumns(e.target.value)}
119
+ />
120
+ </div>
121
+ <div className="flex justify-end mt-3">
122
+ <Button size="sm" intent="action" aria-label="Confirm" onClick={onClick}>
123
+ Confirm
124
+ </Button>
125
+ </div>
126
+ </>
127
+ );
128
+ }
129
+
130
+ export function TablePlugin({
131
+ cellEditorConfig,
132
+ children,
133
+ }: {
134
+ cellEditorConfig: CellEditorConfig;
135
+ children: JSX.Element | Array<JSX.Element>;
136
+ }): JSX.Element | null {
137
+ const [editor] = useLexicalComposerContext();
138
+ const cellContext = useContext(CellContext);
139
+
140
+ useEffect(() => {
141
+ if (!editor.hasNodes([TableNode])) {
142
+ throw new Error('TablePlugin: TableNode is not registered on editor');
143
+ }
144
+
145
+ cellContext.set(cellEditorConfig, children);
146
+
147
+ return editor.registerCommand<InsertTableCommandPayload>(
148
+ INSERT_NEW_TABLE_COMMAND,
149
+ ({ columns, rows, includeHeaders }) => {
150
+ const selection = $getSelection();
151
+
152
+ if (!$isRangeSelection(selection)) {
153
+ return true;
154
+ }
155
+
156
+ const focus = selection.focus;
157
+ const focusNode = focus.getNode();
158
+
159
+ if (focusNode !== null) {
160
+ const tableNode = $createTableNodeWithDimensions(Number(rows), Number(columns), includeHeaders);
161
+
162
+ if ($isRootOrShadowRoot(focusNode)) {
163
+ const target = focusNode.getChildAtIndex(focus.offset);
164
+
165
+ if (target !== null) {
166
+ target.insertBefore(tableNode);
167
+ } else {
168
+ focusNode.append(tableNode);
169
+ }
170
+
171
+ tableNode.insertBefore($createParagraphNode());
172
+ } else {
173
+ const topLevelNode = focusNode.getTopLevelElementOrThrow();
174
+ topLevelNode.insertAfter(tableNode);
175
+ }
176
+
177
+ tableNode.insertAfter($createParagraphNode());
178
+ const nodeSelection = $createNodeSelection();
179
+ nodeSelection.add(tableNode.getKey());
180
+ $setSelection(nodeSelection);
181
+ }
182
+
183
+ return true;
184
+ },
185
+ COMMAND_PRIORITY_EDITOR,
186
+ );
187
+ }, [cellContext, cellEditorConfig, children, editor]);
188
+
189
+ return null;
190
+ }