@pie-lib/editable-html-tip-tap 2.1.1 → 2.1.2-next.102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/CharacterPicker.js +51 -20
- package/lib/components/CharacterPicker.js.map +1 -1
- package/lib/components/EditableHtml.js +37 -24
- package/lib/components/EditableHtml.js.map +1 -1
- package/lib/components/MenuBar.js +15 -6
- package/lib/components/MenuBar.js.map +1 -1
- package/lib/components/TiptapContainer.js +9 -1
- package/lib/components/TiptapContainer.js.map +1 -1
- package/lib/components/common/toolbar-buttons.js +5 -0
- package/lib/components/common/toolbar-buttons.js.map +1 -1
- package/lib/components/respArea/ExplicitConstructedResponse.js +2 -1
- package/lib/components/respArea/ExplicitConstructedResponse.js.map +1 -1
- package/lib/components/respArea/InlineDropdown.js +74 -16
- package/lib/components/respArea/InlineDropdown.js.map +1 -1
- package/lib/components/respArea/inlineDropdownUtils.js +67 -0
- package/lib/components/respArea/inlineDropdownUtils.js.map +1 -0
- package/lib/extensions/css.js +17 -7
- package/lib/extensions/css.js.map +1 -1
- package/lib/extensions/image-component.js +9 -6
- package/lib/extensions/image-component.js.map +1 -1
- package/lib/extensions/math.js +179 -71
- package/lib/extensions/math.js.map +1 -1
- package/lib/extensions/responseArea.js +7 -1
- package/lib/extensions/responseArea.js.map +1 -1
- package/lib/index.js +7 -0
- package/lib/index.js.map +1 -1
- package/lib/utils/toolbar.js +19 -0
- package/lib/utils/toolbar.js.map +1 -0
- package/package.json +18 -18
- package/src/__tests__/EditableHtml.test.jsx +116 -2
- package/src/components/CharacterPicker.jsx +56 -19
- package/src/components/EditableHtml.jsx +43 -23
- package/src/components/MenuBar.jsx +15 -3
- package/src/components/TiptapContainer.jsx +18 -2
- package/src/components/__tests__/CharacterPicker.test.jsx +57 -6
- package/src/components/__tests__/ExplicitConstructedResponse.test.jsx +6 -1
- package/src/components/__tests__/InlineDropdown.test.jsx +185 -8
- package/src/components/__tests__/MenuBar.test.jsx +1 -0
- package/src/components/common/toolbar-buttons.jsx +5 -0
- package/src/components/respArea/ExplicitConstructedResponse.jsx +2 -1
- package/src/components/respArea/InlineDropdown.jsx +97 -24
- package/src/components/respArea/inlineDropdownUtils.js +79 -0
- package/src/extensions/__tests__/image-component.test.jsx +1 -0
- package/src/extensions/__tests__/math.test.js +481 -42
- package/src/extensions/__tests__/responseArea.test.js +57 -3
- package/src/extensions/css.js +16 -3
- package/src/extensions/image-component.jsx +8 -6
- package/src/extensions/math.js +188 -69
- package/src/extensions/responseArea.js +7 -1
- package/src/index.jsx +2 -2
- package/src/utils/__tests__/toolbar.test.js +43 -0
- package/src/utils/toolbar.js +15 -0
|
@@ -29,9 +29,12 @@ export function CharacterPicker({ editor, opts, onClose }) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const containerRef = useRef(null);
|
|
32
|
+
const onCloseRef = useRef(onClose);
|
|
32
33
|
const [position, setPosition] = useState({ top: 0, left: 0 });
|
|
33
34
|
const [popover, setPopover] = useState(null);
|
|
34
35
|
|
|
36
|
+
onCloseRef.current = onClose;
|
|
37
|
+
|
|
35
38
|
const configToUse = useMemo(() => {
|
|
36
39
|
if (!opts) return spanishConfig;
|
|
37
40
|
|
|
@@ -69,31 +72,62 @@ export function CharacterPicker({ editor, opts, onClose }) {
|
|
|
69
72
|
[],
|
|
70
73
|
);
|
|
71
74
|
|
|
75
|
+
// Keep `onClose` out of the dependency array — parents often pass a new callback each
|
|
76
|
+
// render (e.g. after each keystroke), which would re-run this effect constantly. Use a
|
|
77
|
+
// ref so click-outside always calls the latest close handler.
|
|
72
78
|
useEffect(() => {
|
|
73
79
|
if (!editor) return;
|
|
74
80
|
|
|
75
|
-
// Calculate position relative to selection
|
|
76
81
|
const editorDOM = editor.options.element;
|
|
77
|
-
const
|
|
78
|
-
const bodyRect = document.body.getBoundingClientRect();
|
|
79
|
-
const { from } = editor.state.selection;
|
|
80
|
-
const start = editor.view.coordsAtPos(from);
|
|
82
|
+
const editorViewDom = editor.view.dom;
|
|
81
83
|
|
|
82
|
-
|
|
84
|
+
// Position is computed in viewport coordinates (the dialog uses position: fixed),
|
|
85
|
+
// so coordsAtPos / getBoundingClientRect values can be used directly without
|
|
86
|
+
// adding scroll offsets. The dialog is then clamped to the viewport so it does
|
|
87
|
+
// not get cut off by fixed page headers/footers.
|
|
88
|
+
const updatePosition = () => {
|
|
89
|
+
if (!containerRef.current) return;
|
|
83
90
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
const editorRect = editorDOM.getBoundingClientRect();
|
|
92
|
+
const { from } = editor.state.selection;
|
|
93
|
+
const start = editor.view.coordsAtPos(from);
|
|
87
94
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
const dialogHeight = containerRef.current.offsetHeight;
|
|
96
|
+
const dialogWidth = containerRef.current.offsetWidth;
|
|
97
|
+
|
|
98
|
+
// prefer below the editor; flip above when there isn't room below.
|
|
99
|
+
const spaceBelow = window.innerHeight - (editorRect.bottom + 60);
|
|
100
|
+
let top =
|
|
101
|
+
spaceBelow >= dialogHeight || editorRect.top < dialogHeight + 80
|
|
102
|
+
? editorRect.bottom + 60
|
|
103
|
+
: editorRect.top - dialogHeight - 20;
|
|
104
|
+
|
|
105
|
+
let left = start.left;
|
|
106
|
+
|
|
107
|
+
const margin = 8;
|
|
108
|
+
top = Math.max(margin, Math.min(top, window.innerHeight - dialogHeight - margin));
|
|
109
|
+
left = Math.max(margin, Math.min(left, window.innerWidth - dialogWidth - margin));
|
|
110
|
+
|
|
111
|
+
setPosition({ top, left });
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
updatePosition();
|
|
115
|
+
|
|
116
|
+
let frame = null;
|
|
117
|
+
const scheduleUpdate = () => {
|
|
118
|
+
if (frame !== null) return;
|
|
119
|
+
frame = requestAnimationFrame(() => {
|
|
120
|
+
frame = null;
|
|
121
|
+
updatePosition();
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
window.addEventListener('scroll', scheduleUpdate, true);
|
|
126
|
+
window.addEventListener('resize', scheduleUpdate);
|
|
93
127
|
|
|
94
128
|
const handleClickOutside = (e) => {
|
|
95
|
-
if (containerRef.current && !containerRef.current.contains(e.target) && !
|
|
96
|
-
|
|
129
|
+
if (containerRef.current && !containerRef.current.contains(e.target) && !editorViewDom.contains(e.target)) {
|
|
130
|
+
onCloseRef.current();
|
|
97
131
|
}
|
|
98
132
|
};
|
|
99
133
|
|
|
@@ -103,9 +137,12 @@ export function CharacterPicker({ editor, opts, onClose }) {
|
|
|
103
137
|
|
|
104
138
|
return () => {
|
|
105
139
|
clearTimeout(timeoutId);
|
|
140
|
+
if (frame !== null) cancelAnimationFrame(frame);
|
|
141
|
+
window.removeEventListener('scroll', scheduleUpdate, true);
|
|
142
|
+
window.removeEventListener('resize', scheduleUpdate);
|
|
106
143
|
document.removeEventListener('click', handleClickOutside);
|
|
107
144
|
};
|
|
108
|
-
}, [editor
|
|
145
|
+
}, [editor]);
|
|
109
146
|
|
|
110
147
|
const renderPopOver = (event, el) => setPopover({ anchorEl: event.currentTarget, el });
|
|
111
148
|
|
|
@@ -124,11 +161,11 @@ export function CharacterPicker({ editor, opts, onClose }) {
|
|
|
124
161
|
data-toolbar-for={editor.instanceId}
|
|
125
162
|
style={{
|
|
126
163
|
visibility: position.top === 0 && position.left === 0 ? 'hidden' : 'initial',
|
|
127
|
-
position: '
|
|
164
|
+
position: 'fixed',
|
|
128
165
|
top: `${position.top}px`,
|
|
129
166
|
left: `${position.left}px`,
|
|
130
167
|
maxWidth: '500px',
|
|
131
|
-
zIndex:
|
|
168
|
+
zIndex: 1000,
|
|
132
169
|
}}
|
|
133
170
|
>
|
|
134
171
|
<div>
|
|
@@ -120,6 +120,30 @@ export const EditableHtml = (props) => {
|
|
|
120
120
|
...toolbarOpts,
|
|
121
121
|
};
|
|
122
122
|
|
|
123
|
+
const commitEditorContent = useCallback(
|
|
124
|
+
(editor) => {
|
|
125
|
+
const html = editor.getHTML();
|
|
126
|
+
|
|
127
|
+
if (props.markup !== html) {
|
|
128
|
+
props.onChange?.(html);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (toolbarOptsToUse.doneOn === 'blur') {
|
|
132
|
+
props.onDone?.(html);
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
[props.markup, props.onChange, props.onDone, toolbarOptsToUse.doneOn],
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const responseAreaPropsToUse = useMemo(
|
|
139
|
+
() => ({
|
|
140
|
+
...defaultResponseAreaProps,
|
|
141
|
+
...props.responseAreaProps,
|
|
142
|
+
onInlineDropdownToolbarClose: commitEditorContent,
|
|
143
|
+
}),
|
|
144
|
+
[props.responseAreaProps, commitEditorContent],
|
|
145
|
+
);
|
|
146
|
+
|
|
123
147
|
const activePluginsToUse = useMemo(() => {
|
|
124
148
|
let { customPlugins, ...otherPluginProps } = props.pluginProps || {};
|
|
125
149
|
|
|
@@ -143,14 +167,14 @@ export const EditableHtml = (props) => {
|
|
|
143
167
|
toolbar: {},
|
|
144
168
|
table: {},
|
|
145
169
|
responseArea: {
|
|
146
|
-
type:
|
|
170
|
+
type: responseAreaPropsToUse.type,
|
|
147
171
|
},
|
|
148
172
|
languageCharacters: props.languageCharactersProps,
|
|
149
173
|
keyPadCharacterRef: {},
|
|
150
174
|
setKeypadInteraction: {},
|
|
151
175
|
media: {},
|
|
152
176
|
});
|
|
153
|
-
}, [props]);
|
|
177
|
+
}, [props, responseAreaPropsToUse.type]);
|
|
154
178
|
|
|
155
179
|
const extensions = [
|
|
156
180
|
TextAlign.configure({
|
|
@@ -183,11 +207,11 @@ export const EditableHtml = (props) => {
|
|
|
183
207
|
TableRow,
|
|
184
208
|
ExtendedTableHeader,
|
|
185
209
|
ExtendedTableCell,
|
|
186
|
-
ResponseAreaExtension.configure(
|
|
187
|
-
ExplicitConstructedResponseNode.configure(
|
|
188
|
-
DragInTheBlankNode.configure(
|
|
189
|
-
InlineDropdownNode.configure(
|
|
190
|
-
MathTemplatedNode.configure(
|
|
210
|
+
ResponseAreaExtension.configure(responseAreaPropsToUse),
|
|
211
|
+
ExplicitConstructedResponseNode.configure(responseAreaPropsToUse),
|
|
212
|
+
DragInTheBlankNode.configure(responseAreaPropsToUse),
|
|
213
|
+
InlineDropdownNode.configure(responseAreaPropsToUse),
|
|
214
|
+
MathTemplatedNode.configure(responseAreaPropsToUse),
|
|
191
215
|
MathNode.configure({
|
|
192
216
|
toolbarOpts: toolbarOptsToUse,
|
|
193
217
|
math: props.pluginProps?.math || {},
|
|
@@ -219,7 +243,7 @@ export const EditableHtml = (props) => {
|
|
|
219
243
|
let cb;
|
|
220
244
|
|
|
221
245
|
if (scheduled && result) {
|
|
222
|
-
|
|
246
|
+
// finish editing only on success
|
|
223
247
|
cb = props.onChange;
|
|
224
248
|
}
|
|
225
249
|
|
|
@@ -288,29 +312,23 @@ export const EditableHtml = (props) => {
|
|
|
288
312
|
editable: !props.disabled,
|
|
289
313
|
content: normalizeInitialMarkup(props.markup),
|
|
290
314
|
onUpdate: ({ editor, transaction }) => {
|
|
291
|
-
if (transaction.isDone
|
|
315
|
+
if (transaction.isDone) {
|
|
292
316
|
props.onChange?.(editor.getHTML());
|
|
293
317
|
}
|
|
294
318
|
},
|
|
295
|
-
onBlur:
|
|
319
|
+
onBlur: ({ editor }) => {
|
|
296
320
|
const otherToolbarOpened =
|
|
297
321
|
editor._insertingImage ||
|
|
298
322
|
editor._toolbarOpened ||
|
|
299
323
|
editor.isActive('inline_dropdown') ||
|
|
300
324
|
editor.isActive('explicit_constructed_response');
|
|
301
325
|
|
|
302
|
-
if (otherToolbarOpened) {
|
|
326
|
+
if (otherToolbarOpened || !editor.schema) {
|
|
303
327
|
return;
|
|
304
328
|
}
|
|
305
329
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (toolbarOptsToUse.doneOn === 'blur') {
|
|
311
|
-
props.onDone?.(editor.getHTML());
|
|
312
|
-
}
|
|
313
|
-
}, 200),
|
|
330
|
+
commitEditorContent(editor);
|
|
331
|
+
},
|
|
314
332
|
},
|
|
315
333
|
[props.charactersLimit],
|
|
316
334
|
);
|
|
@@ -332,7 +350,7 @@ export const EditableHtml = (props) => {
|
|
|
332
350
|
const nextMarkup = normalizeInitialMarkup(props.markup);
|
|
333
351
|
|
|
334
352
|
if (nextMarkup !== editor.getHTML()) {
|
|
335
|
-
editor.commands.setContent(nextMarkup, false);
|
|
353
|
+
editor.commands.setContent(nextMarkup, { emitUpdate: false });
|
|
336
354
|
}
|
|
337
355
|
}, [props.markup, editor]);
|
|
338
356
|
|
|
@@ -406,10 +424,12 @@ const StyledEditorContent = styled(EditorContent, {
|
|
|
406
424
|
},
|
|
407
425
|
|
|
408
426
|
// Out of flow so the caret stays at the start of the block; in-flow ::before pushes the caret after the hint text.
|
|
409
|
-
|
|
427
|
+
// :only-child ensures the placeholder is hidden whenever the editor has other content (images, upload nodes, etc.)
|
|
428
|
+
// and covers the type+backspace edge case where Tiptap only adds is-empty (not is-editor-empty).
|
|
429
|
+
'& p[data-placeholder].is-empty:only-child, & div[data-placeholder].is-empty:only-child': {
|
|
410
430
|
position: 'relative',
|
|
411
431
|
},
|
|
412
|
-
'& p.is-
|
|
432
|
+
'& p[data-placeholder].is-empty:only-child::before, & div[data-placeholder].is-empty:only-child::before': {
|
|
413
433
|
content: 'attr(data-placeholder)',
|
|
414
434
|
position: 'absolute',
|
|
415
435
|
left: 0,
|
|
@@ -428,7 +448,7 @@ const StyledEditorContent = styled(EditorContent, {
|
|
|
428
448
|
},
|
|
429
449
|
}),
|
|
430
450
|
...(separateParagraph && {
|
|
431
|
-
'& >
|
|
451
|
+
'& > p:has(+ p)': {
|
|
432
452
|
marginBottom: '1em',
|
|
433
453
|
},
|
|
434
454
|
}),
|
|
@@ -86,6 +86,10 @@ function MenuBar({
|
|
|
86
86
|
|
|
87
87
|
let currentNode;
|
|
88
88
|
|
|
89
|
+
if (!ctx.editor?.commandManager) {
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
|
|
89
93
|
if (selection instanceof NodeSelection) {
|
|
90
94
|
currentNode = selection.node; // the selected node
|
|
91
95
|
}
|
|
@@ -103,6 +107,7 @@ function MenuBar({
|
|
|
103
107
|
hideDefaultToolbar,
|
|
104
108
|
hasTextSelectionInTable,
|
|
105
109
|
isFocused: ctx.editor?.isFocused,
|
|
110
|
+
toolbarOpened: ctx.editor?._toolbarOpened ?? false,
|
|
106
111
|
isBold: ctx.editor.isActive('bold') ?? false,
|
|
107
112
|
canBold: ctx.editor.can().chain().toggleBold().run() ?? false,
|
|
108
113
|
isTable: ctx.editor.isActive('table') ?? false,
|
|
@@ -143,7 +148,9 @@ function MenuBar({
|
|
|
143
148
|
[classes.toolbarWithNoDone]: !hasDoneButton,
|
|
144
149
|
[classes.toolbarTop]: toolbarOpts.position === 'top',
|
|
145
150
|
[classes.toolbarRight]: toolbarOpts.alignment === 'right',
|
|
146
|
-
[classes.focused]:
|
|
151
|
+
[classes.focused]:
|
|
152
|
+
toolbarOpts.alwaysVisible ||
|
|
153
|
+
(editorState.isFocused && !editorState.toolbarOpened && !editorState.hideDefaultToolbar),
|
|
147
154
|
[classes.autoWidth]: autoWidth,
|
|
148
155
|
[classes.fullWidth]: !autoWidth,
|
|
149
156
|
[classes.hidden]: toolbarOpts.isHidden === true,
|
|
@@ -431,7 +438,7 @@ const StyledMenuBar = (props) => {
|
|
|
431
438
|
const classes = {
|
|
432
439
|
defaultToolbar: 'defaultToolbar',
|
|
433
440
|
buttonsContainer: 'buttonsContainer',
|
|
434
|
-
button: '
|
|
441
|
+
button: 'toolbarButton',
|
|
435
442
|
active: 'active',
|
|
436
443
|
disabled: 'disabled',
|
|
437
444
|
isActive: 'isActive',
|
|
@@ -466,13 +473,18 @@ const StyledMenuBarRoot = styled('div')(({ theme }) => ({
|
|
|
466
473
|
display: 'flex',
|
|
467
474
|
width: '100%',
|
|
468
475
|
},
|
|
469
|
-
'& .
|
|
476
|
+
'& .toolbarButton': {
|
|
470
477
|
color: 'grey',
|
|
471
478
|
display: 'inline-flex',
|
|
472
479
|
padding: '2px',
|
|
473
480
|
background: 'none',
|
|
474
481
|
border: 'none',
|
|
475
482
|
cursor: 'pointer',
|
|
483
|
+
// previously we had implicit 24×24 icon rendering for mui svg icons, but now we need to explicitly set the size to 24×24 to match the previous behavior
|
|
484
|
+
'& svg': {
|
|
485
|
+
width: '24px',
|
|
486
|
+
height: '24px',
|
|
487
|
+
},
|
|
476
488
|
'&:hover': {
|
|
477
489
|
color: 'black',
|
|
478
490
|
},
|
|
@@ -168,12 +168,28 @@ function TiptapContainer(props) {
|
|
|
168
168
|
|
|
169
169
|
el.style.visibility = 'hidden';
|
|
170
170
|
el.style.position = 'absolute';
|
|
171
|
+
el.style.whiteSpace = 'nowrap';
|
|
171
172
|
el.textContent = 'W'.repeat(props.charactersLimit);
|
|
172
173
|
|
|
173
|
-
|
|
174
|
+
// Copy font styles from the editor container so the width measurement reflects the actual font
|
|
175
|
+
const computedStyles = window.getComputedStyle(rootRef.current);
|
|
176
|
+
const fontStyleProps = [
|
|
177
|
+
'fontFamily',
|
|
178
|
+
'fontSize',
|
|
179
|
+
'fontWeight',
|
|
180
|
+
'fontStyle',
|
|
181
|
+
'letterSpacing',
|
|
182
|
+
'wordSpacing',
|
|
183
|
+
'textTransform',
|
|
184
|
+
'lineHeight',
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
fontStyleProps.forEach((prop) => {
|
|
188
|
+
el.style[prop] = computedStyles[prop];
|
|
189
|
+
});
|
|
174
190
|
|
|
191
|
+
document.body.appendChild(el);
|
|
175
192
|
setAdjustedWidth(`${el.offsetWidth + 27}px`);
|
|
176
|
-
|
|
177
193
|
el.remove();
|
|
178
194
|
}
|
|
179
195
|
}, [props.adjustWidthForLimit, props.charactersLimit]);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { fireEvent, render, waitFor } from '@testing-library/react';
|
|
2
|
+
import { act, fireEvent, render, waitFor } from '@testing-library/react';
|
|
3
3
|
import { CharacterIcon, CharacterPicker } from '../CharacterPicker';
|
|
4
4
|
|
|
5
5
|
jest.mock('react-dom', () => ({
|
|
@@ -128,13 +128,55 @@ describe('CharacterPicker', () => {
|
|
|
128
128
|
};
|
|
129
129
|
render(<CharacterPicker editor={mockEditor} opts={opts} onClose={onClose} />);
|
|
130
130
|
|
|
131
|
+
await act(async () => {
|
|
132
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
133
|
+
});
|
|
134
|
+
fireEvent.click(document.body);
|
|
135
|
+
|
|
131
136
|
await waitFor(() => {
|
|
132
|
-
|
|
133
|
-
fireEvent.click(document.body);
|
|
134
|
-
}, 0);
|
|
137
|
+
expect(onClose).toHaveBeenCalled();
|
|
135
138
|
});
|
|
139
|
+
});
|
|
136
140
|
|
|
137
|
-
|
|
141
|
+
it('does not re-run positioning when only onClose reference changes', async () => {
|
|
142
|
+
const opts = {
|
|
143
|
+
characters: [['á', 'é']],
|
|
144
|
+
};
|
|
145
|
+
const getRect = mockEditor.options.element.getBoundingClientRect;
|
|
146
|
+
const { rerender } = render(<CharacterPicker editor={mockEditor} opts={opts} onClose={jest.fn()} />);
|
|
147
|
+
|
|
148
|
+
await act(async () => {
|
|
149
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const callsAfterMount = getRect.mock.calls.length;
|
|
153
|
+
expect(callsAfterMount).toBeGreaterThan(0);
|
|
154
|
+
|
|
155
|
+
rerender(<CharacterPicker editor={mockEditor} opts={opts} onClose={jest.fn()} />);
|
|
156
|
+
|
|
157
|
+
expect(getRect.mock.calls.length).toBe(callsAfterMount);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('outside click invokes the latest onClose after rerender', async () => {
|
|
161
|
+
const opts = {
|
|
162
|
+
characters: [['á', 'é']],
|
|
163
|
+
};
|
|
164
|
+
const onCloseFirst = jest.fn();
|
|
165
|
+
const { rerender } = render(<CharacterPicker editor={mockEditor} opts={opts} onClose={onCloseFirst} />);
|
|
166
|
+
|
|
167
|
+
await act(async () => {
|
|
168
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const onCloseSecond = jest.fn();
|
|
172
|
+
rerender(<CharacterPicker editor={mockEditor} opts={opts} onClose={onCloseSecond} />);
|
|
173
|
+
|
|
174
|
+
fireEvent.click(document.body);
|
|
175
|
+
|
|
176
|
+
await waitFor(() => {
|
|
177
|
+
expect(onCloseSecond).toHaveBeenCalled();
|
|
178
|
+
});
|
|
179
|
+
expect(onCloseFirst).not.toHaveBeenCalled();
|
|
138
180
|
});
|
|
139
181
|
|
|
140
182
|
it('does not close when clicking inside picker', async () => {
|
|
@@ -192,7 +234,16 @@ describe('CharacterPicker', () => {
|
|
|
192
234
|
};
|
|
193
235
|
const { container } = render(<CharacterPicker editor={mockEditor} opts={opts} onClose={jest.fn()} />);
|
|
194
236
|
const dialog = container.querySelector('.insert-character-dialog');
|
|
195
|
-
expect(dialog).toHaveStyle({ position: '
|
|
237
|
+
expect(dialog).toHaveStyle({ position: 'fixed' });
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('renders above other editor overlays with a high z-index', () => {
|
|
241
|
+
const opts = {
|
|
242
|
+
characters: [['á']],
|
|
243
|
+
};
|
|
244
|
+
const { container } = render(<CharacterPicker editor={mockEditor} opts={opts} onClose={jest.fn()} />);
|
|
245
|
+
const dialog = container.querySelector('.insert-character-dialog');
|
|
246
|
+
expect(dialog).toHaveStyle({ zIndex: '1000' });
|
|
196
247
|
});
|
|
197
248
|
|
|
198
249
|
it('adds data-toolbar-for attribute with editor instanceId', () => {
|
|
@@ -19,6 +19,9 @@ describe('ExplicitConstructedResponse', () => {
|
|
|
19
19
|
const buildMockEditor = (overrides = {}) => {
|
|
20
20
|
const mockTr = {
|
|
21
21
|
delete: jest.fn(),
|
|
22
|
+
setMeta: jest.fn(function setMeta() {
|
|
23
|
+
return mockTr;
|
|
24
|
+
}),
|
|
22
25
|
};
|
|
23
26
|
return {
|
|
24
27
|
state: {
|
|
@@ -197,7 +200,9 @@ describe('ExplicitConstructedResponse', () => {
|
|
|
197
200
|
const { findByLabelText } = render(<ExplicitConstructedResponse {...defaultProps} selected />);
|
|
198
201
|
fireEvent.mouseDown(await findByLabelText('Delete'));
|
|
199
202
|
expect(mockEditor.state.tr.delete).toHaveBeenCalledWith(5, 6);
|
|
200
|
-
expect(mockEditor.view.dispatch).
|
|
203
|
+
expect(mockEditor.view.dispatch).toHaveBeenCalledTimes(2);
|
|
204
|
+
expect(mockEditor.view.dispatch).toHaveBeenNthCalledWith(1, mockEditor.state.tr);
|
|
205
|
+
expect(mockEditor.view.dispatch).toHaveBeenNthCalledWith(2, mockEditor.state.tr);
|
|
201
206
|
expect(mockEditor._toolbarOpened).toBe(false);
|
|
202
207
|
expect(mockEditor.commands.focus).toHaveBeenCalled();
|
|
203
208
|
});
|