@pie-lib/editable-html-tip-tap 2.1.2-next.0 → 2.1.2-next.104

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 (280) hide show
  1. package/CHANGELOG.json +32 -0
  2. package/CHANGELOG.md +2532 -0
  3. package/LICENSE.md +5 -0
  4. package/lib/components/CharacterPicker.js +226 -0
  5. package/lib/components/CharacterPicker.js.map +1 -0
  6. package/lib/components/EditableHtml.js +388 -0
  7. package/lib/components/EditableHtml.js.map +1 -0
  8. package/lib/components/MenuBar.js +702 -0
  9. package/lib/components/MenuBar.js.map +1 -0
  10. package/lib/components/TiptapContainer.js +242 -0
  11. package/lib/components/TiptapContainer.js.map +1 -0
  12. package/lib/components/characters/characterUtils.js +378 -0
  13. package/lib/components/characters/characterUtils.js.map +1 -0
  14. package/lib/components/characters/custom-popper.js +44 -0
  15. package/lib/components/characters/custom-popper.js.map +1 -0
  16. package/lib/components/common/done-button.js +34 -0
  17. package/lib/components/common/done-button.js.map +1 -0
  18. package/lib/components/common/toolbar-buttons.js +149 -0
  19. package/lib/components/common/toolbar-buttons.js.map +1 -0
  20. package/lib/components/icons/CssIcon.js +25 -0
  21. package/lib/components/icons/CssIcon.js.map +1 -0
  22. package/lib/components/icons/RespArea.js +72 -0
  23. package/lib/components/icons/RespArea.js.map +1 -0
  24. package/lib/components/icons/TableIcons.js +53 -0
  25. package/lib/components/icons/TableIcons.js.map +1 -0
  26. package/lib/components/icons/TextAlign.js +157 -0
  27. package/lib/components/icons/TextAlign.js.map +1 -0
  28. package/lib/components/image/AltDialog.js +98 -0
  29. package/lib/components/image/AltDialog.js.map +1 -0
  30. package/lib/components/image/ImageToolbar.js +137 -0
  31. package/lib/components/image/ImageToolbar.js.map +1 -0
  32. package/lib/components/image/InsertImageHandler.js +135 -0
  33. package/lib/components/image/InsertImageHandler.js.map +1 -0
  34. package/lib/components/media/MediaDialog.js +594 -0
  35. package/lib/components/media/MediaDialog.js.map +1 -0
  36. package/lib/components/media/MediaToolbar.js +74 -0
  37. package/lib/components/media/MediaToolbar.js.map +1 -0
  38. package/lib/components/media/MediaWrapper.js +67 -0
  39. package/lib/components/media/MediaWrapper.js.map +1 -0
  40. package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js +84 -0
  41. package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js.map +1 -0
  42. package/lib/components/respArea/DragInTheBlank/choice.js +253 -0
  43. package/lib/components/respArea/DragInTheBlank/choice.js.map +1 -0
  44. package/lib/components/respArea/ExplicitConstructedResponse.js +137 -0
  45. package/lib/components/respArea/ExplicitConstructedResponse.js.map +1 -0
  46. package/lib/components/respArea/InlineDropdown.js +223 -0
  47. package/lib/components/respArea/InlineDropdown.js.map +1 -0
  48. package/lib/components/respArea/MathTemplated.js +130 -0
  49. package/lib/components/respArea/MathTemplated.js.map +1 -0
  50. package/lib/components/respArea/ToolbarIcon.js +81 -0
  51. package/lib/components/respArea/ToolbarIcon.js.map +1 -0
  52. package/lib/components/respArea/inlineDropdownUtils.js +67 -0
  53. package/lib/components/respArea/inlineDropdownUtils.js.map +1 -0
  54. package/lib/constants.js +11 -0
  55. package/lib/constants.js.map +1 -0
  56. package/lib/extensions/css.js +227 -0
  57. package/lib/extensions/css.js.map +1 -0
  58. package/lib/extensions/custom-toolbar-wrapper.js +92 -0
  59. package/lib/extensions/custom-toolbar-wrapper.js.map +1 -0
  60. package/lib/extensions/div-node.js +83 -0
  61. package/lib/extensions/div-node.js.map +1 -0
  62. package/lib/extensions/ensure-empty-root-div.js +48 -0
  63. package/lib/extensions/ensure-empty-root-div.js.map +1 -0
  64. package/lib/extensions/ensure-list-item-content-is-div.js +64 -0
  65. package/lib/extensions/ensure-list-item-content-is-div.js.map +1 -0
  66. package/lib/extensions/extended-list-item.js +15 -0
  67. package/lib/extensions/extended-list-item.js.map +1 -0
  68. package/lib/extensions/extended-table-cell.js +22 -0
  69. package/lib/extensions/extended-table-cell.js.map +1 -0
  70. package/lib/extensions/extended-table.js +75 -0
  71. package/lib/extensions/extended-table.js.map +1 -0
  72. package/lib/extensions/heading-paragraph.js +61 -0
  73. package/lib/extensions/heading-paragraph.js.map +1 -0
  74. package/lib/extensions/image-component.js +351 -0
  75. package/lib/extensions/image-component.js.map +1 -0
  76. package/lib/extensions/image.js +134 -0
  77. package/lib/extensions/image.js.map +1 -0
  78. package/lib/extensions/index.js +46 -0
  79. package/lib/extensions/index.js.map +1 -0
  80. package/lib/extensions/math.js +451 -0
  81. package/lib/extensions/math.js.map +1 -0
  82. package/lib/extensions/media.js +243 -0
  83. package/lib/extensions/media.js.map +1 -0
  84. package/lib/extensions/responseArea.js +452 -0
  85. package/lib/extensions/responseArea.js.map +1 -0
  86. package/lib/index.js +37 -0
  87. package/lib/index.js.map +1 -0
  88. package/lib/styles/editorContainerStyles.js +137 -0
  89. package/lib/styles/editorContainerStyles.js.map +1 -0
  90. package/lib/theme.js +8 -0
  91. package/lib/theme.js.map +1 -0
  92. package/lib/utils/helper.js +73 -0
  93. package/lib/utils/helper.js.map +1 -0
  94. package/lib/utils/size.js +26 -0
  95. package/lib/utils/size.js.map +1 -0
  96. package/lib/utils/toolbar.js +19 -0
  97. package/lib/utils/toolbar.js.map +1 -0
  98. package/package.json +22 -35
  99. package/src/__tests__/EditableHtml.test.jsx +588 -0
  100. package/src/__tests__/constants.test.js +19 -0
  101. package/src/__tests__/div-to-paragraph-conversion.test.jsx +125 -0
  102. package/src/__tests__/extensions.test.js +208 -0
  103. package/src/__tests__/index.test.jsx +154 -0
  104. package/src/__tests__/size-utils.test.js +64 -0
  105. package/src/__tests__/theme.test.js +17 -0
  106. package/src/components/CharacterPicker.jsx +237 -0
  107. package/src/components/EditableHtml.jsx +458 -0
  108. package/src/components/MenuBar.jsx +561 -0
  109. package/src/components/TiptapContainer.jsx +235 -0
  110. package/src/components/__tests__/AltDialog.test.jsx +147 -0
  111. package/src/components/__tests__/CharacterPicker.test.jsx +270 -0
  112. package/src/components/__tests__/CssIcon.test.jsx +46 -0
  113. package/src/components/__tests__/DragInTheBlank.test.jsx +255 -0
  114. package/src/components/__tests__/ExplicitConstructedResponse.test.jsx +209 -0
  115. package/src/components/__tests__/ImageToolbar.test.jsx +128 -0
  116. package/src/components/__tests__/InlineDropdown.test.jsx +557 -0
  117. package/src/components/__tests__/InsertImageHandler.test.js +161 -0
  118. package/src/components/__tests__/MediaDialog.test.jsx +293 -0
  119. package/src/components/__tests__/MediaToolbar.test.jsx +74 -0
  120. package/src/components/__tests__/MediaWrapper.test.jsx +81 -0
  121. package/src/components/__tests__/MenuBar.test.jsx +250 -0
  122. package/src/components/__tests__/RespArea.test.jsx +122 -0
  123. package/src/components/__tests__/TableIcons.test.jsx +149 -0
  124. package/src/components/__tests__/TextAlign.test.jsx +167 -0
  125. package/src/components/__tests__/TiptapContainer.test.jsx +138 -0
  126. package/src/components/__tests__/characterUtils.test.js +166 -0
  127. package/src/components/__tests__/choice.test.jsx +171 -0
  128. package/src/components/__tests__/custom-popper.test.jsx +82 -0
  129. package/src/components/__tests__/done-button.test.jsx +54 -0
  130. package/src/components/__tests__/toolbar-buttons.test.jsx +234 -0
  131. package/src/components/characters/characterUtils.js +447 -0
  132. package/src/components/characters/custom-popper.js +38 -0
  133. package/src/components/common/done-button.jsx +27 -0
  134. package/src/components/common/toolbar-buttons.jsx +127 -0
  135. package/src/components/icons/CssIcon.jsx +15 -0
  136. package/src/components/icons/RespArea.jsx +71 -0
  137. package/src/components/icons/TableIcons.jsx +52 -0
  138. package/src/components/icons/TextAlign.jsx +114 -0
  139. package/src/components/image/AltDialog.jsx +82 -0
  140. package/src/components/image/ImageToolbar.jsx +99 -0
  141. package/src/components/image/InsertImageHandler.js +107 -0
  142. package/src/components/media/MediaDialog.jsx +596 -0
  143. package/src/components/media/MediaToolbar.jsx +49 -0
  144. package/src/components/media/MediaWrapper.jsx +39 -0
  145. package/src/components/respArea/DragInTheBlank/DragInTheBlank.jsx +76 -0
  146. package/src/components/respArea/DragInTheBlank/__tests__/choice.test.jsx +72 -0
  147. package/src/components/respArea/DragInTheBlank/choice.jsx +259 -0
  148. package/src/components/respArea/ExplicitConstructedResponse.jsx +136 -0
  149. package/src/components/respArea/InlineDropdown.jsx +240 -0
  150. package/src/components/respArea/MathTemplated.jsx +124 -0
  151. package/src/components/respArea/ToolbarIcon.jsx +66 -0
  152. package/src/components/respArea/__tests__/MathTemplated.test.jsx +210 -0
  153. package/src/components/respArea/inlineDropdownUtils.js +79 -0
  154. package/src/constants.js +5 -0
  155. package/src/extensions/__tests__/css.test.js +196 -0
  156. package/src/extensions/__tests__/custom-toolbar-wrapper.test.jsx +180 -0
  157. package/src/extensions/__tests__/divNode.test.js +87 -0
  158. package/src/extensions/__tests__/ensure-empty-root-div.test.js +57 -0
  159. package/src/extensions/__tests__/ensure-list-item-content-is-div.test.js +44 -0
  160. package/src/extensions/__tests__/extended-list-item.test.js +13 -0
  161. package/src/extensions/__tests__/extended-table-cell.test.js +22 -0
  162. package/src/extensions/__tests__/extended-table.test.js +183 -0
  163. package/src/extensions/__tests__/image-component.test.jsx +346 -0
  164. package/src/extensions/__tests__/image.test.js +237 -0
  165. package/src/extensions/__tests__/math.test.js +898 -0
  166. package/src/extensions/__tests__/media-node-view.test.jsx +298 -0
  167. package/src/extensions/__tests__/media.test.js +271 -0
  168. package/src/extensions/__tests__/responseArea.test.js +655 -0
  169. package/src/extensions/css.js +233 -0
  170. package/src/extensions/custom-toolbar-wrapper.jsx +78 -0
  171. package/src/extensions/div-node.js +86 -0
  172. package/src/extensions/ensure-empty-root-div.js +47 -0
  173. package/src/extensions/ensure-list-item-content-is-div.js +62 -0
  174. package/src/extensions/extended-list-item.js +10 -0
  175. package/src/extensions/extended-table-cell.js +19 -0
  176. package/src/extensions/extended-table.js +60 -0
  177. package/src/extensions/heading-paragraph.js +53 -0
  178. package/src/extensions/image-component.jsx +340 -0
  179. package/src/extensions/image.js +109 -0
  180. package/src/extensions/index.js +81 -0
  181. package/src/extensions/math.js +446 -0
  182. package/src/extensions/media.js +188 -0
  183. package/src/extensions/responseArea.js +407 -0
  184. package/src/index.jsx +5 -0
  185. package/src/styles/editorContainerStyles.js +145 -0
  186. package/src/theme.js +1 -0
  187. package/src/utils/__tests__/helper.test.js +126 -0
  188. package/src/utils/__tests__/toolbar.test.js +43 -0
  189. package/src/utils/helper.js +69 -0
  190. package/src/utils/size.js +32 -0
  191. package/src/utils/toolbar.js +15 -0
  192. package/dist/components/CharacterPicker.d.ts +0 -31
  193. package/dist/components/CharacterPicker.js +0 -131
  194. package/dist/components/EditableHtml.d.ts +0 -11
  195. package/dist/components/EditableHtml.js +0 -291
  196. package/dist/components/MenuBar.d.ts +0 -11
  197. package/dist/components/MenuBar.js +0 -462
  198. package/dist/components/TiptapContainer.d.ts +0 -11
  199. package/dist/components/TiptapContainer.js +0 -154
  200. package/dist/components/characters/characterUtils.d.ts +0 -35
  201. package/dist/components/characters/characterUtils.js +0 -465
  202. package/dist/components/characters/custom-popper.d.ts +0 -14
  203. package/dist/components/characters/custom-popper.js +0 -32
  204. package/dist/components/common/done-button.d.ts +0 -30
  205. package/dist/components/common/done-button.js +0 -26
  206. package/dist/components/common/toolbar-buttons.d.ts +0 -38
  207. package/dist/components/common/toolbar-buttons.js +0 -91
  208. package/dist/components/icons/CssIcon.d.ts +0 -11
  209. package/dist/components/icons/CssIcon.js +0 -14
  210. package/dist/components/icons/RespArea.d.ts +0 -26
  211. package/dist/components/icons/RespArea.js +0 -42
  212. package/dist/components/icons/TableIcons.d.ts +0 -14
  213. package/dist/components/icons/TableIcons.js +0 -32
  214. package/dist/components/icons/TextAlign.d.ts +0 -18
  215. package/dist/components/icons/TextAlign.js +0 -134
  216. package/dist/components/image/AltDialog.d.ts +0 -22
  217. package/dist/components/image/AltDialog.js +0 -61
  218. package/dist/components/image/ImageToolbar.d.ts +0 -24
  219. package/dist/components/image/ImageToolbar.js +0 -80
  220. package/dist/components/image/InsertImageHandler.d.ts +0 -32
  221. package/dist/components/image/InsertImageHandler.js +0 -53
  222. package/dist/components/media/MediaDialog.d.ts +0 -43
  223. package/dist/components/media/MediaDialog.js +0 -389
  224. package/dist/components/media/MediaToolbar.d.ts +0 -19
  225. package/dist/components/media/MediaToolbar.js +0 -41
  226. package/dist/components/media/MediaWrapper.d.ts +0 -19
  227. package/dist/components/respArea/DragInTheBlank/DragInTheBlank.d.ts +0 -23
  228. package/dist/components/respArea/DragInTheBlank/DragInTheBlank.js +0 -58
  229. package/dist/components/respArea/DragInTheBlank/choice.d.ts +0 -56
  230. package/dist/components/respArea/DragInTheBlank/choice.js +0 -156
  231. package/dist/components/respArea/ExplicitConstructedResponse.d.ts +0 -20
  232. package/dist/components/respArea/ExplicitConstructedResponse.js +0 -83
  233. package/dist/components/respArea/InlineDropdown.d.ts +0 -18
  234. package/dist/components/respArea/InlineDropdown.js +0 -119
  235. package/dist/components/respArea/MathTemplated.d.ts +0 -19
  236. package/dist/components/respArea/MathTemplated.js +0 -97
  237. package/dist/components/respArea/ToolbarIcon.d.ts +0 -14
  238. package/dist/components/respArea/ToolbarIcon.js +0 -17
  239. package/dist/components/respArea/inlineDropdownUtils.d.ts +0 -15
  240. package/dist/components/respArea/inlineDropdownUtils.js +0 -15
  241. package/dist/constants.d.ts +0 -13
  242. package/dist/constants.js +0 -4
  243. package/dist/extensions/css.d.ts +0 -11
  244. package/dist/extensions/css.js +0 -115
  245. package/dist/extensions/custom-toolbar-wrapper.d.ts +0 -11
  246. package/dist/extensions/custom-toolbar-wrapper.js +0 -61
  247. package/dist/extensions/div-node.d.ts +0 -10
  248. package/dist/extensions/div-node.js +0 -42
  249. package/dist/extensions/ensure-empty-root-div.d.ts +0 -14
  250. package/dist/extensions/ensure-empty-root-div.js +0 -24
  251. package/dist/extensions/ensure-list-item-content-is-div.d.ts +0 -15
  252. package/dist/extensions/ensure-list-item-content-is-div.js +0 -31
  253. package/dist/extensions/extended-list-item.d.ts +0 -13
  254. package/dist/extensions/extended-list-item.js +0 -5
  255. package/dist/extensions/extended-table-cell.d.ts +0 -10
  256. package/dist/extensions/extended-table-cell.js +0 -6
  257. package/dist/extensions/extended-table.d.ts +0 -17
  258. package/dist/extensions/extended-table.js +0 -34
  259. package/dist/extensions/heading-paragraph.d.ts +0 -17
  260. package/dist/extensions/heading-paragraph.js +0 -30
  261. package/dist/extensions/image-component.d.ts +0 -22
  262. package/dist/extensions/image-component.js +0 -220
  263. package/dist/extensions/image.d.ts +0 -10
  264. package/dist/extensions/image.js +0 -68
  265. package/dist/extensions/index.d.ts +0 -16
  266. package/dist/extensions/index.js +0 -65
  267. package/dist/extensions/math.d.ts +0 -15
  268. package/dist/extensions/math.js +0 -158
  269. package/dist/extensions/media.d.ts +0 -19
  270. package/dist/extensions/media.js +0 -149
  271. package/dist/extensions/responseArea.d.ts +0 -27
  272. package/dist/extensions/responseArea.js +0 -259
  273. package/dist/index.d.ts +0 -13
  274. package/dist/index.js +0 -7
  275. package/dist/styles/editorContainerStyles.d.ts +0 -134
  276. package/dist/theme.d.ts +0 -9
  277. package/dist/utils/helper.d.ts +0 -9
  278. package/dist/utils/helper.js +0 -27
  279. package/dist/utils/size.d.ts +0 -9
  280. package/dist/utils/size.js +0 -14
@@ -0,0 +1,240 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import { renderMath } from '@pie-lib/math-rendering';
4
+ import PropTypes from 'prop-types';
5
+ import { NodeViewWrapper } from '@tiptap/react';
6
+ import { NodeSelection } from 'prosemirror-state';
7
+ import { Chevron } from '../icons/RespArea';
8
+ import CustomToolbarWrapper from '../../extensions/custom-toolbar-wrapper';
9
+ import { setToolbarOpened } from '../../utils/toolbar';
10
+
11
+ const InlineDropdown = (props) => {
12
+ const { editor, node, getPos, options, selected } = props;
13
+ const { attrs: attributes } = node;
14
+ const { value, error } = attributes;
15
+ const html = value || '<div>&nbsp</div>';
16
+ const pos = getPos();
17
+ const toolbarRef = useRef(null);
18
+ const toolbarEditor = useRef(null);
19
+ const pendingCloseRequest = useRef(false);
20
+ const elementRef = useRef(null);
21
+
22
+ const isHeld = () =>
23
+ editor._holdInlineDropdownToolbarIndex != null &&
24
+ String(editor._holdInlineDropdownToolbarIndex) === String(node.attrs.index);
25
+
26
+ const [showToolbar, setShowToolbar] = useState(false);
27
+ const [position, setPosition] = useState({ top: 0, left: 0 });
28
+
29
+ const closeToolbar = () => {
30
+ if (isHeld()) {
31
+ return;
32
+ }
33
+
34
+ setShowToolbar(false);
35
+ options.onInlineDropdownToolbarClose?.(editor);
36
+ };
37
+
38
+ const InlineDropdownToolbar = options.respAreaToolbar([node, pos], editor, closeToolbar);
39
+
40
+ const reselectNode = () => {
41
+ const { tr } = editor.state;
42
+ const nodeAtPos = tr.doc.nodeAt(pos);
43
+
44
+ if (!nodeAtPos) {
45
+ return;
46
+ }
47
+
48
+ const { selection } = tr;
49
+
50
+ if (selection.from === pos && selection.to === pos + nodeAtPos.nodeSize) {
51
+ return;
52
+ }
53
+
54
+ tr.setSelection(NodeSelection.create(tr.doc, pos));
55
+ editor.view.dispatch(tr);
56
+ };
57
+
58
+ const requestClose = () => {
59
+ if (pendingCloseRequest.current) {
60
+ return;
61
+ }
62
+
63
+ if (options.onToolbarCloseRequest) {
64
+ pendingCloseRequest.current = true;
65
+
66
+ options.onToolbarCloseRequest(
67
+ [node, pos],
68
+ editor,
69
+ () => {
70
+ pendingCloseRequest.current = false;
71
+ delete editor._holdInlineDropdownToolbarIndex;
72
+ closeToolbar();
73
+ },
74
+ () => {
75
+ pendingCloseRequest.current = false;
76
+ delete editor._holdInlineDropdownToolbarIndex;
77
+ setShowToolbar(true);
78
+ setTimeout(reselectNode, 0);
79
+ },
80
+ );
81
+ } else {
82
+ closeToolbar();
83
+ }
84
+ };
85
+
86
+ useEffect(() => {
87
+ const { selection } = editor.state;
88
+ const onlyThisNodeSelected = selection.from + node.nodeSize === selection.to;
89
+
90
+ if (selected) {
91
+ if (onlyThisNodeSelected) {
92
+ setShowToolbar(true);
93
+ }
94
+ } else if (showToolbar) {
95
+ requestClose();
96
+ }
97
+ }, [editor, node, selected]);
98
+
99
+ const isScrollbarClicked = (event) =>
100
+ event.clientX > document.documentElement.clientWidth ||
101
+ event.clientY > document.documentElement.clientHeight ||
102
+ event.target === document.documentElement;
103
+
104
+ useEffect(() => {
105
+ if (elementRef.current && typeof renderMath === 'function') {
106
+ renderMath(elementRef.current);
107
+ }
108
+ }, [value]);
109
+
110
+ useEffect(() => {
111
+ // Calculate position relative to selection
112
+ const bodyRect = document.body.getBoundingClientRect();
113
+ const { from } = editor.state.selection;
114
+ const start = editor.view.coordsAtPos(from);
115
+
116
+ setPosition({
117
+ top: start.top + Math.abs(bodyRect.top) + 40, // shift above
118
+ left: start.left,
119
+ });
120
+
121
+ const handleClickOutside = (event) => {
122
+ if (isScrollbarClicked(event)) {
123
+ return;
124
+ }
125
+
126
+ const insideSomeEditor = event.target.closest('[data-toolbar-for]');
127
+
128
+ if (
129
+ !event.target.closest('[data-inline-dropdown-toolbar]') &&
130
+ (!insideSomeEditor || insideSomeEditor.dataset.toolbarFor !== toolbarEditor.current?.instanceId) &&
131
+ !editor._toolbarOpened &&
132
+ toolbarRef.current &&
133
+ !toolbarRef.current.contains(event.target) &&
134
+ !event.target.closest('[data-inline-node]')
135
+ ) {
136
+ requestClose();
137
+ }
138
+ };
139
+
140
+ if (showToolbar) {
141
+ document.addEventListener('mousedown', handleClickOutside);
142
+ } else {
143
+ document.removeEventListener('mousedown', handleClickOutside);
144
+ }
145
+
146
+ return () => document.removeEventListener('mousedown', handleClickOutside);
147
+ }, [showToolbar, node]);
148
+
149
+ return (
150
+ <NodeViewWrapper
151
+ className="inline-dropdown"
152
+ data-selected={selected}
153
+ style={{
154
+ display: 'inline-flex',
155
+ height: '50px',
156
+ cursor: 'pointer',
157
+ }}
158
+ >
159
+ <div
160
+ ref={elementRef}
161
+ style={{
162
+ display: 'inline-flex',
163
+ minWidth: '178px',
164
+ height: '36px',
165
+ background: '#FFF',
166
+ border: '1px solid #C0C3CF',
167
+ boxSizing: 'border-box',
168
+ borderRadius: '3px',
169
+ margin: '0 2px',
170
+ position: 'relative',
171
+ alignItems: 'center',
172
+ }}
173
+ onClick={() => setShowToolbar(true)}
174
+ >
175
+ <div
176
+ style={{
177
+ flex: 1,
178
+ overflow: 'hidden',
179
+ padding: '0 25px 0 8px',
180
+ whiteSpace: 'nowrap',
181
+ textOverflow: 'ellipsis',
182
+ }}
183
+ >
184
+ <span
185
+ style={{
186
+ display: 'inline-block',
187
+ verticalAlign: 'middle',
188
+ }}
189
+ dangerouslySetInnerHTML={{ __html: html }}
190
+ />
191
+ </div>
192
+ <Chevron direction="down" style={{ position: 'absolute', top: '5px', right: '5px' }} />
193
+ </div>
194
+ {showToolbar && (
195
+ <React.Fragment>
196
+ {ReactDOM.createPortal(
197
+ <div ref={toolbarRef}>
198
+ <InlineDropdownToolbar
199
+ editorCallback={(instance) => {
200
+ toolbarEditor.current = instance;
201
+ }}
202
+ />
203
+ </div>,
204
+ editor?._tiptapContainerEl || document.body,
205
+ )}
206
+
207
+ {editor._tiptapContainerEl &&
208
+ ReactDOM.createPortal(
209
+ <CustomToolbarWrapper
210
+ deletable
211
+ toolbarOpts={{ minWidth: 'auto' }}
212
+ autoWidth
213
+ style={{ top: -40, left: 0, right: 0 }}
214
+ onDelete={() => {
215
+ const { tr } = editor.state;
216
+ tr.delete(pos, pos + node.nodeSize);
217
+ // Prevent the debounced onBlur/onDone from firing into the
218
+ // now-deleted node's stale position
219
+ setToolbarOpened(editor, false);
220
+ delete editor._holdInlineDropdownToolbarIndex;
221
+ editor.view.dispatch(tr);
222
+ setShowToolbar(false);
223
+ editor.commands.focus();
224
+ }}
225
+ showDone={false}
226
+ />,
227
+ editor._tiptapContainerEl,
228
+ )}
229
+ </React.Fragment>
230
+ )}
231
+ </NodeViewWrapper>
232
+ );
233
+ };
234
+
235
+ InlineDropdown.propTypes = {
236
+ attributes: PropTypes.object,
237
+ selectedItem: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
238
+ };
239
+
240
+ export default InlineDropdown;
@@ -0,0 +1,124 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { NodeViewWrapper } from '@tiptap/react';
4
+ import { mq } from '@pie-lib/math-input';
5
+ import { styled } from '@mui/material/styles';
6
+
7
+ const StyledSpanContainer = styled('span')(() => ({
8
+ display: 'inline-flex',
9
+ border: '1px solid #C0C3CF',
10
+ margin: '1px 5px',
11
+ cursor: 'pointer',
12
+ alignItems: 'center',
13
+ justifyContent: 'center',
14
+ minWidth: '50px',
15
+ minHeight: '36px',
16
+ height: 'fit-content',
17
+ }));
18
+
19
+ const StyledResponseBox = styled('div')(({ theme }) => ({
20
+ background: theme.palette.grey['A100'],
21
+ color: theme.palette.grey['A700'],
22
+ display: 'inline-flex',
23
+ borderRight: '2px solid #C0C3CF',
24
+ boxSizing: 'border-box',
25
+ overflow: 'hidden',
26
+ fontSize: '12px',
27
+ minHeight: '36px',
28
+ height: '100%',
29
+ alignItems: 'center',
30
+ fontFamily: 'Symbola, Times New Roman, serif',
31
+ padding: '0 2px',
32
+ }));
33
+
34
+ const StyledMathBlock = styled('div')(() => ({
35
+ flex: 8,
36
+ color: 'var(--pie-text, black)',
37
+ padding: '4px !important',
38
+ display: 'flex',
39
+ alignItems: 'center',
40
+ justifyContent: 'center',
41
+ backgroundColor: 'var(--pie-background, rgba(255, 255, 255, 0))',
42
+ '& > .mq-math-mode sup.mq-nthroot': {
43
+ fontSize: '70% !important',
44
+ verticalAlign: '1em !important',
45
+ },
46
+ '& > .mq-math-mode .mq-sqrt-stem': {
47
+ borderTop: '0.07em solid',
48
+ marginLeft: '-1.5px',
49
+ marginTop: '-2px !important',
50
+ paddingTop: '5px !important',
51
+ },
52
+ '& .mq-overarrow-inner': {
53
+ paddingTop: '0 !important',
54
+ border: 'none !important',
55
+ },
56
+ '& .mq-overarrow.mq-arrow-both': {
57
+ marginTop: '0px',
58
+ minWidth: '1.23em',
59
+ '& *': {
60
+ lineHeight: '1 !important',
61
+ },
62
+ '&:before': {
63
+ top: '-0.4em',
64
+ left: '-1px',
65
+ },
66
+ '&:after': {
67
+ top: '0px !important',
68
+ position: 'absolute !important',
69
+ right: '-2px',
70
+ },
71
+ '&.mq-empty:after': {
72
+ top: '-0.45em',
73
+ },
74
+ },
75
+ '& .mq-overarrow.mq-arrow-right': {
76
+ '&:before': {
77
+ top: '-0.4em',
78
+ right: '-1px',
79
+ },
80
+ },
81
+ '& .mq-overarrow-inner-right': {
82
+ display: 'none !important',
83
+ },
84
+ '& .mq-overarrow-inner-left': {
85
+ display: 'none !important',
86
+ },
87
+ }));
88
+ const MathTemplated = (props) => {
89
+ const { node, options, selected } = props;
90
+ const { attrs: attributes } = node;
91
+ const { value, index } = attributes;
92
+
93
+ // add 1 to index to display R 1 instead of R 0
94
+ const keyToDisplay = `R ${parseInt(index) + 1}`;
95
+
96
+ // console.log({nodeProps.children})
97
+ return (
98
+ <NodeViewWrapper
99
+ className="math-templated"
100
+ data-selected={selected}
101
+ style={{
102
+ display: 'inline-flex',
103
+ minHeight: '36px',
104
+ minWidth: '50px',
105
+ cursor: 'pointer',
106
+ }}
107
+ >
108
+ <StyledSpanContainer {...attributes}>
109
+ <StyledResponseBox>{keyToDisplay}</StyledResponseBox>
110
+ <StyledMathBlock>
111
+ <mq.Static latex={value} />
112
+ </StyledMathBlock>
113
+ </StyledSpanContainer>
114
+ </NodeViewWrapper>
115
+ );
116
+ };
117
+
118
+ MathTemplated.propTypes = {
119
+ attributes: PropTypes.object,
120
+ value: PropTypes.string,
121
+ keyToDisplay: PropTypes.string,
122
+ };
123
+
124
+ export default MathTemplated;
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+ import { styled } from '@mui/material/styles';
3
+
4
+ export const Chevron = (props) => {
5
+ const { direction, style } = props;
6
+ const rotate = (() => {
7
+ switch (direction) {
8
+ case 'down':
9
+ return 90;
10
+ case 'up':
11
+ return -90;
12
+ case 'left':
13
+ return 180;
14
+ default:
15
+ return 0;
16
+ }
17
+ })();
18
+
19
+ return (
20
+ <svg
21
+ style={{
22
+ transform: `rotate(${rotate}deg)`,
23
+ ...style,
24
+ }}
25
+ width="24"
26
+ height="24"
27
+ viewBox="0 0 24 24"
28
+ xmlns="http://www.w3.org/2000/svg"
29
+ >
30
+ <path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" fill="currentColor" />
31
+ </svg>
32
+ );
33
+ };
34
+
35
+ export const GripIcon = ({ style }) => (
36
+ <span style={style}>
37
+ <svg
38
+ style={{
39
+ margin: '0 -16px',
40
+ }}
41
+ width="24"
42
+ height="24"
43
+ viewBox="0 0 24 24"
44
+ xmlns="http://www.w3.org/2000/svg"
45
+ >
46
+ <path d="M9 3H7v2h2V3zm0 4H7v2h2V7zm0 4H7v2h2v-2zm0 4H7v2h2v-2zm0 4H7v2h2v-2zm4-16h-2v2h2V3zm0 4h-2v2h2V7zm0 4h-2v2h2v-2zm0 4h-2v2h2v-2zm0 4h-2v2h2v-2z" />
47
+ </svg>
48
+ <svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
49
+ <path d="M9 3H7v2h2V3zm0 4H7v2h2V7zm0 4H7v2h2v-2zm0 4H7v2h2v-2zm0 4H7v2h2v-2zm4-16h-2v2h2V3zm0 4h-2v2h2V7zm0 4h-2v2h2v-2zm0 4h-2v2h2v-2zm0 4h-2v2h2v-2z" />
50
+ </svg>
51
+ </span>
52
+ );
53
+
54
+ const StyledToolbarIcon = styled('div')(({ theme }) => ({
55
+ fontFamily: 'Cerebri Sans !important',
56
+ fontSize: theme.typography.fontSize,
57
+ fontWeight: 'bold',
58
+ lineHeight: '14px',
59
+ position: 'relative',
60
+ top: '7px',
61
+ width: '110px',
62
+ height: '28px',
63
+ whiteSpace: 'nowrap',
64
+ }));
65
+
66
+ export const ToolbarIcon = () => <StyledToolbarIcon>+ Response Area</StyledToolbarIcon>;
@@ -0,0 +1,210 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import MathTemplated from '../MathTemplated';
4
+
5
+ // Mock the dependencies
6
+ jest.mock('@tiptap/react', () => ({
7
+ NodeViewWrapper: ({ children, ...props }) => (
8
+ <div data-testid="node-view-wrapper" {...props}>
9
+ {children}
10
+ </div>
11
+ ),
12
+ }));
13
+
14
+ jest.mock('@pie-lib/math-input', () => ({
15
+ mq: {
16
+ Static: ({ latex }) => (
17
+ <div data-testid="mq-static" data-latex={latex}>
18
+ {latex}
19
+ </div>
20
+ ),
21
+ },
22
+ }));
23
+
24
+ describe('MathTemplated', () => {
25
+ const defaultProps = {
26
+ node: {
27
+ attrs: {
28
+ value: 'x^2 + y^2 = r^2',
29
+ index: 0,
30
+ },
31
+ },
32
+ options: {},
33
+ selected: false,
34
+ };
35
+
36
+ it('renders without crashing', () => {
37
+ const { container } = render(<MathTemplated {...defaultProps} />);
38
+ expect(container).toBeInTheDocument();
39
+ });
40
+
41
+ it('renders NodeViewWrapper with correct className', () => {
42
+ const { getByTestId } = render(<MathTemplated {...defaultProps} />);
43
+ const wrapper = getByTestId('node-view-wrapper');
44
+ expect(wrapper).toHaveClass('math-templated');
45
+ });
46
+
47
+ it('displays correct response key for index 0', () => {
48
+ const { getByText } = render(<MathTemplated {...defaultProps} />);
49
+ expect(getByText('R 1')).toBeInTheDocument();
50
+ });
51
+
52
+ it('displays correct response key for index 1', () => {
53
+ const props = {
54
+ ...defaultProps,
55
+ node: {
56
+ attrs: {
57
+ value: 'a + b',
58
+ index: 1,
59
+ },
60
+ },
61
+ };
62
+ const { getByText } = render(<MathTemplated {...props} />);
63
+ expect(getByText('R 2')).toBeInTheDocument();
64
+ });
65
+
66
+ it('displays correct response key for index 5', () => {
67
+ const props = {
68
+ ...defaultProps,
69
+ node: {
70
+ attrs: {
71
+ value: 'c = d',
72
+ index: 5,
73
+ },
74
+ },
75
+ };
76
+ const { getByText } = render(<MathTemplated {...props} />);
77
+ expect(getByText('R 6')).toBeInTheDocument();
78
+ });
79
+
80
+ it('renders LaTeX value correctly', () => {
81
+ const { getByTestId } = render(<MathTemplated {...defaultProps} />);
82
+ const mqStatic = getByTestId('mq-static');
83
+ expect(mqStatic).toHaveAttribute('data-latex', 'x^2 + y^2 = r^2');
84
+ });
85
+
86
+ it('renders different LaTeX value', () => {
87
+ const props = {
88
+ ...defaultProps,
89
+ node: {
90
+ attrs: {
91
+ value: '\\frac{a}{b}',
92
+ index: 2,
93
+ },
94
+ },
95
+ };
96
+ const { getByTestId } = render(<MathTemplated {...props} />);
97
+ const mqStatic = getByTestId('mq-static');
98
+ expect(mqStatic).toHaveAttribute('data-latex', '\\frac{a}{b}');
99
+ });
100
+
101
+ it('passes selected prop to NodeViewWrapper', () => {
102
+ const props = {
103
+ ...defaultProps,
104
+ selected: true,
105
+ };
106
+ const { getByTestId } = render(<MathTemplated {...props} />);
107
+ const wrapper = getByTestId('node-view-wrapper');
108
+ expect(wrapper).toHaveAttribute('data-selected', 'true');
109
+ });
110
+
111
+ it('passes false selected prop to NodeViewWrapper', () => {
112
+ const { getByTestId } = render(<MathTemplated {...defaultProps} />);
113
+ const wrapper = getByTestId('node-view-wrapper');
114
+ expect(wrapper).toHaveAttribute('data-selected', 'false');
115
+ });
116
+
117
+ it('applies correct inline styles to NodeViewWrapper', () => {
118
+ const { getByTestId } = render(<MathTemplated {...defaultProps} />);
119
+ const wrapper = getByTestId('node-view-wrapper');
120
+ expect(wrapper).toHaveStyle({
121
+ display: 'inline-flex',
122
+ minHeight: '36px',
123
+ minWidth: '50px',
124
+ cursor: 'pointer',
125
+ });
126
+ });
127
+
128
+ it('handles string index correctly', () => {
129
+ const props = {
130
+ ...defaultProps,
131
+ node: {
132
+ attrs: {
133
+ value: 'test',
134
+ index: '3',
135
+ },
136
+ },
137
+ };
138
+ const { getByText } = render(<MathTemplated {...props} />);
139
+ expect(getByText('R 4')).toBeInTheDocument();
140
+ });
141
+
142
+ it('handles empty value', () => {
143
+ const props = {
144
+ ...defaultProps,
145
+ node: {
146
+ attrs: {
147
+ value: '',
148
+ index: 0,
149
+ },
150
+ },
151
+ };
152
+ const { getByTestId } = render(<MathTemplated {...props} />);
153
+ const mqStatic = getByTestId('mq-static');
154
+ expect(mqStatic).toHaveAttribute('data-latex', '');
155
+ });
156
+
157
+ it('renders all styled components', () => {
158
+ const { container, getByText } = render(<MathTemplated {...defaultProps} />);
159
+
160
+ // Check for response box
161
+ expect(getByText('R 1')).toBeInTheDocument();
162
+
163
+ // Check for math block with LaTeX
164
+ const mqStatic = container.querySelector('[data-testid="mq-static"]');
165
+ expect(mqStatic).toBeInTheDocument();
166
+ });
167
+
168
+ it('handles zero index', () => {
169
+ const props = {
170
+ ...defaultProps,
171
+ node: {
172
+ attrs: {
173
+ value: 'x = 0',
174
+ index: 0,
175
+ },
176
+ },
177
+ };
178
+ const { getByText } = render(<MathTemplated {...props} />);
179
+ expect(getByText('R 1')).toBeInTheDocument();
180
+ });
181
+
182
+ it('handles large index values', () => {
183
+ const props = {
184
+ ...defaultProps,
185
+ node: {
186
+ attrs: {
187
+ value: 'x = 100',
188
+ index: 99,
189
+ },
190
+ },
191
+ };
192
+ const { getByText } = render(<MathTemplated {...props} />);
193
+ expect(getByText('R 100')).toBeInTheDocument();
194
+ });
195
+
196
+ it('renders with complex LaTeX expression', () => {
197
+ const props = {
198
+ ...defaultProps,
199
+ node: {
200
+ attrs: {
201
+ value: '\\sqrt{x^2 + y^2}',
202
+ index: 0,
203
+ },
204
+ },
205
+ };
206
+ const { getByTestId } = render(<MathTemplated {...props} />);
207
+ const mqStatic = getByTestId('mq-static');
208
+ expect(mqStatic).toHaveAttribute('data-latex', '\\sqrt{x^2 + y^2}');
209
+ });
210
+ });