@pie-lib/editable-html-tip-tap 2.1.10 → 2.2.0-beta

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 (114) hide show
  1. package/CHANGELOG.md +0 -75
  2. package/lib/__tests__/EditableHtml.test.js +831 -0
  3. package/lib/__tests__/EditableHtml.test.js.map +1 -0
  4. package/lib/__tests__/constants.test.js +21 -0
  5. package/lib/__tests__/constants.test.js.map +1 -0
  6. package/lib/__tests__/div-to-paragraph-conversion.test.js +213 -0
  7. package/lib/__tests__/div-to-paragraph-conversion.test.js.map +1 -0
  8. package/lib/__tests__/extensions.test.js +209 -0
  9. package/lib/__tests__/extensions.test.js.map +1 -0
  10. package/lib/__tests__/index.test.js +250 -0
  11. package/lib/__tests__/index.test.js.map +1 -0
  12. package/lib/__tests__/size-utils.test.js +57 -0
  13. package/lib/__tests__/size-utils.test.js.map +1 -0
  14. package/lib/__tests__/theme.test.js +17 -0
  15. package/lib/__tests__/theme.test.js.map +1 -0
  16. package/lib/components/EditableHtml.js +4 -2
  17. package/lib/components/EditableHtml.js.map +1 -1
  18. package/lib/components/__tests__/AltDialog.test.js +201 -0
  19. package/lib/components/__tests__/AltDialog.test.js.map +1 -0
  20. package/lib/components/__tests__/CharacterPicker.test.js +455 -0
  21. package/lib/components/__tests__/CharacterPicker.test.js.map +1 -0
  22. package/lib/components/__tests__/CssIcon.test.js +58 -0
  23. package/lib/components/__tests__/CssIcon.test.js.map +1 -0
  24. package/lib/components/__tests__/DragInTheBlank.test.js +295 -0
  25. package/lib/components/__tests__/DragInTheBlank.test.js.map +1 -0
  26. package/lib/components/__tests__/ExplicitConstructedResponse.test.js +345 -0
  27. package/lib/components/__tests__/ExplicitConstructedResponse.test.js.map +1 -0
  28. package/lib/components/__tests__/ImageToolbar.test.js +185 -0
  29. package/lib/components/__tests__/ImageToolbar.test.js.map +1 -0
  30. package/lib/components/__tests__/InlineDropdown.test.js +839 -0
  31. package/lib/components/__tests__/InlineDropdown.test.js.map +1 -0
  32. package/lib/components/__tests__/InsertImageHandler.test.js +171 -0
  33. package/lib/components/__tests__/InsertImageHandler.test.js.map +1 -0
  34. package/lib/components/__tests__/MediaDialog.test.js +433 -0
  35. package/lib/components/__tests__/MediaDialog.test.js.map +1 -0
  36. package/lib/components/__tests__/MediaToolbar.test.js +126 -0
  37. package/lib/components/__tests__/MediaToolbar.test.js.map +1 -0
  38. package/lib/components/__tests__/MediaWrapper.test.js +96 -0
  39. package/lib/components/__tests__/MediaWrapper.test.js.map +1 -0
  40. package/lib/components/__tests__/MenuBar.test.js +502 -0
  41. package/lib/components/__tests__/MenuBar.test.js.map +1 -0
  42. package/lib/components/__tests__/RespArea.test.js +171 -0
  43. package/lib/components/__tests__/RespArea.test.js.map +1 -0
  44. package/lib/components/__tests__/TableIcons.test.js +153 -0
  45. package/lib/components/__tests__/TableIcons.test.js.map +1 -0
  46. package/lib/components/__tests__/TextAlign.test.js +209 -0
  47. package/lib/components/__tests__/TextAlign.test.js.map +1 -0
  48. package/lib/components/__tests__/TiptapContainer.test.js +196 -0
  49. package/lib/components/__tests__/TiptapContainer.test.js.map +1 -0
  50. package/lib/components/__tests__/characterUtils.test.js +178 -0
  51. package/lib/components/__tests__/characterUtils.test.js.map +1 -0
  52. package/lib/components/__tests__/choice.test.js +213 -0
  53. package/lib/components/__tests__/choice.test.js.map +1 -0
  54. package/lib/components/__tests__/custom-popper.test.js +108 -0
  55. package/lib/components/__tests__/custom-popper.test.js.map +1 -0
  56. package/lib/components/__tests__/done-button.test.js +72 -0
  57. package/lib/components/__tests__/done-button.test.js.map +1 -0
  58. package/lib/components/__tests__/toolbar-buttons.test.js +277 -0
  59. package/lib/components/__tests__/toolbar-buttons.test.js.map +1 -0
  60. package/lib/components/respArea/DragInTheBlank/__tests__/choice.test.js +90 -0
  61. package/lib/components/respArea/DragInTheBlank/__tests__/choice.test.js.map +1 -0
  62. package/lib/components/respArea/DragInTheBlank/choice.js +3 -0
  63. package/lib/components/respArea/DragInTheBlank/choice.js.map +1 -1
  64. package/lib/components/respArea/MathTemplated.js +2 -2
  65. package/lib/components/respArea/MathTemplated.js.map +1 -1
  66. package/lib/components/respArea/__tests__/MathTemplated.test.js +217 -0
  67. package/lib/components/respArea/__tests__/MathTemplated.test.js.map +1 -0
  68. package/lib/extensions/__tests__/css.test.js +214 -0
  69. package/lib/extensions/__tests__/css.test.js.map +1 -0
  70. package/lib/extensions/__tests__/custom-toolbar-wrapper.test.js +175 -0
  71. package/lib/extensions/__tests__/custom-toolbar-wrapper.test.js.map +1 -0
  72. package/lib/extensions/__tests__/divNode.test.js +108 -0
  73. package/lib/extensions/__tests__/divNode.test.js.map +1 -0
  74. package/lib/extensions/__tests__/ensure-empty-root-div.test.js +50 -0
  75. package/lib/extensions/__tests__/ensure-empty-root-div.test.js.map +1 -0
  76. package/lib/extensions/__tests__/ensure-list-item-content-is-div.test.js +60 -0
  77. package/lib/extensions/__tests__/ensure-list-item-content-is-div.test.js.map +1 -0
  78. package/lib/extensions/__tests__/extended-list-item.test.js +13 -0
  79. package/lib/extensions/__tests__/extended-list-item.test.js.map +1 -0
  80. package/lib/extensions/__tests__/extended-table-cell.test.js +15 -0
  81. package/lib/extensions/__tests__/extended-table-cell.test.js.map +1 -0
  82. package/lib/extensions/__tests__/extended-table.test.js +200 -0
  83. package/lib/extensions/__tests__/extended-table.test.js.map +1 -0
  84. package/lib/extensions/__tests__/image-component.test.js +432 -0
  85. package/lib/extensions/__tests__/image-component.test.js.map +1 -0
  86. package/lib/extensions/__tests__/image.test.js +313 -0
  87. package/lib/extensions/__tests__/image.test.js.map +1 -0
  88. package/lib/extensions/__tests__/math.test.js +1399 -0
  89. package/lib/extensions/__tests__/math.test.js.map +1 -0
  90. package/lib/extensions/__tests__/media-node-view.test.js +307 -0
  91. package/lib/extensions/__tests__/media-node-view.test.js.map +1 -0
  92. package/lib/extensions/__tests__/media.test.js +292 -0
  93. package/lib/extensions/__tests__/media.test.js.map +1 -0
  94. package/lib/extensions/__tests__/responseArea.test.js +736 -0
  95. package/lib/extensions/__tests__/responseArea.test.js.map +1 -0
  96. package/lib/extensions/image-component.js +9 -6
  97. package/lib/extensions/image-component.js.map +1 -1
  98. package/lib/extensions/responseArea.js +7 -1
  99. package/lib/extensions/responseArea.js.map +1 -1
  100. package/lib/utils/__tests__/helper.test.js +107 -0
  101. package/lib/utils/__tests__/helper.test.js.map +1 -0
  102. package/lib/utils/__tests__/toolbar.test.js +38 -0
  103. package/lib/utils/__tests__/toolbar.test.js.map +1 -0
  104. package/package.json +7 -7
  105. package/src/components/EditableHtml.jsx +5 -3
  106. package/src/components/respArea/DragInTheBlank/__tests__/choice.test.jsx +72 -0
  107. package/src/components/respArea/DragInTheBlank/choice.jsx +3 -0
  108. package/src/components/respArea/MathTemplated.jsx +1 -1
  109. package/src/components/respArea/__tests__/MathTemplated.test.jsx +1 -1
  110. package/src/extensions/__tests__/image-component.test.jsx +1 -0
  111. package/src/extensions/__tests__/responseArea.test.js +57 -3
  112. package/src/extensions/image-component.jsx +8 -6
  113. package/src/extensions/responseArea.js +7 -1
  114. package/LICENSE.md +0 -5
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+
3
+ var _toolbar = require("../toolbar");
4
+ describe('setToolbarOpened', function () {
5
+ var createEditor = function createEditor() {
6
+ var toolbarOpened = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
7
+ var tr = {
8
+ setMeta: jest.fn().mockReturnThis()
9
+ };
10
+ return {
11
+ _toolbarOpened: toolbarOpened,
12
+ state: {
13
+ tr: tr
14
+ },
15
+ view: {
16
+ dispatch: jest.fn()
17
+ }
18
+ };
19
+ };
20
+ it('sets editor._toolbarOpened and dispatches a meta transaction', function () {
21
+ var editor = createEditor(false);
22
+ (0, _toolbar.setToolbarOpened)(editor, true);
23
+ expect(editor._toolbarOpened).toBe(true);
24
+ expect(editor.state.tr.setMeta).toHaveBeenCalledWith(_toolbar.TOOLBAR_OPENED_META_KEY, true);
25
+ expect(editor.view.dispatch).toHaveBeenCalledWith(editor.state.tr);
26
+ });
27
+ it('does nothing when the value is unchanged', function () {
28
+ var editor = createEditor(true);
29
+ (0, _toolbar.setToolbarOpened)(editor, true);
30
+ expect(editor.view.dispatch).not.toHaveBeenCalled();
31
+ });
32
+ it('coerces the opened value to a boolean', function () {
33
+ var editor = createEditor(false);
34
+ (0, _toolbar.setToolbarOpened)(editor, 1);
35
+ expect(editor._toolbarOpened).toBe(true);
36
+ });
37
+ });
38
+ //# sourceMappingURL=toolbar.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolbar.test.js","names":["_toolbar","require","describe","createEditor","toolbarOpened","arguments","length","undefined","tr","setMeta","jest","fn","mockReturnThis","_toolbarOpened","state","view","dispatch","it","editor","setToolbarOpened","expect","toBe","toHaveBeenCalledWith","TOOLBAR_OPENED_META_KEY","not","toHaveBeenCalled"],"sources":["../../../src/utils/__tests__/toolbar.test.js"],"sourcesContent":["import { setToolbarOpened, TOOLBAR_OPENED_META_KEY } from '../toolbar';\n\ndescribe('setToolbarOpened', () => {\n const createEditor = (toolbarOpened = false) => {\n const tr = {\n setMeta: jest.fn().mockReturnThis(),\n };\n\n return {\n _toolbarOpened: toolbarOpened,\n state: { tr },\n view: {\n dispatch: jest.fn(),\n },\n };\n };\n\n it('sets editor._toolbarOpened and dispatches a meta transaction', () => {\n const editor = createEditor(false);\n\n setToolbarOpened(editor, true);\n\n expect(editor._toolbarOpened).toBe(true);\n expect(editor.state.tr.setMeta).toHaveBeenCalledWith(TOOLBAR_OPENED_META_KEY, true);\n expect(editor.view.dispatch).toHaveBeenCalledWith(editor.state.tr);\n });\n\n it('does nothing when the value is unchanged', () => {\n const editor = createEditor(true);\n\n setToolbarOpened(editor, true);\n\n expect(editor.view.dispatch).not.toHaveBeenCalled();\n });\n\n it('coerces the opened value to a boolean', () => {\n const editor = createEditor(false);\n\n setToolbarOpened(editor, 1);\n\n expect(editor._toolbarOpened).toBe(true);\n });\n});\n"],"mappings":";;AAAA,IAAAA,QAAA,GAAAC,OAAA;AAEAC,QAAQ,CAAC,kBAAkB,EAAE,YAAM;EACjC,IAAMC,YAAY,GAAG,SAAfA,YAAYA,CAAA,EAA8B;IAAA,IAA1BC,aAAa,GAAAC,SAAA,CAAAC,MAAA,QAAAD,SAAA,QAAAE,SAAA,GAAAF,SAAA,MAAG,KAAK;IACzC,IAAMG,EAAE,GAAG;MACTC,OAAO,EAAEC,IAAI,CAACC,EAAE,CAAC,CAAC,CAACC,cAAc,CAAC;IACpC,CAAC;IAED,OAAO;MACLC,cAAc,EAAET,aAAa;MAC7BU,KAAK,EAAE;QAAEN,EAAE,EAAFA;MAAG,CAAC;MACbO,IAAI,EAAE;QACJC,QAAQ,EAAEN,IAAI,CAACC,EAAE,CAAC;MACpB;IACF,CAAC;EACH,CAAC;EAEDM,EAAE,CAAC,8DAA8D,EAAE,YAAM;IACvE,IAAMC,MAAM,GAAGf,YAAY,CAAC,KAAK,CAAC;IAElC,IAAAgB,yBAAgB,EAACD,MAAM,EAAE,IAAI,CAAC;IAE9BE,MAAM,CAACF,MAAM,CAACL,cAAc,CAAC,CAACQ,IAAI,CAAC,IAAI,CAAC;IACxCD,MAAM,CAACF,MAAM,CAACJ,KAAK,CAACN,EAAE,CAACC,OAAO,CAAC,CAACa,oBAAoB,CAACC,gCAAuB,EAAE,IAAI,CAAC;IACnFH,MAAM,CAACF,MAAM,CAACH,IAAI,CAACC,QAAQ,CAAC,CAACM,oBAAoB,CAACJ,MAAM,CAACJ,KAAK,CAACN,EAAE,CAAC;EACpE,CAAC,CAAC;EAEFS,EAAE,CAAC,0CAA0C,EAAE,YAAM;IACnD,IAAMC,MAAM,GAAGf,YAAY,CAAC,IAAI,CAAC;IAEjC,IAAAgB,yBAAgB,EAACD,MAAM,EAAE,IAAI,CAAC;IAE9BE,MAAM,CAACF,MAAM,CAACH,IAAI,CAACC,QAAQ,CAAC,CAACQ,GAAG,CAACC,gBAAgB,CAAC,CAAC;EACrD,CAAC,CAAC;EAEFR,EAAE,CAAC,uCAAuC,EAAE,YAAM;IAChD,IAAMC,MAAM,GAAGf,YAAY,CAAC,KAAK,CAAC;IAElC,IAAAgB,yBAAgB,EAACD,MAAM,EAAE,CAAC,CAAC;IAE3BE,MAAM,CAACF,MAAM,CAACL,cAAc,CAAC,CAACQ,IAAI,CAAC,IAAI,CAAC;EAC1C,CAAC,CAAC;AACJ,CAAC,CAAC","ignoreList":[]}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "2.1.10",
6
+ "version": "2.2.0-beta",
7
7
  "description": "",
8
8
  "license": "ISC",
9
9
  "main": "lib/index.js",
@@ -16,11 +16,11 @@
16
16
  "@dnd-kit/utilities": "3.2.2",
17
17
  "@mui/icons-material": "^7.3.4",
18
18
  "@mui/material": "^7.3.4",
19
- "@pie-lib/drag": "^4.0.6",
20
- "@pie-lib/math-input": "^8.1.1",
21
- "@pie-lib/math-rendering": "^5.1.0",
22
- "@pie-lib/math-toolbar": "^3.0.6",
23
- "@pie-lib/render-ui": "^6.1.3",
19
+ "@pie-lib/drag": "^4.0.2",
20
+ "@pie-lib/math-input-mathlive": "^1.0.1",
21
+ "@pie-lib/math-rendering": "^5.0.2",
22
+ "@pie-lib/math-toolbar": "^3.1.0-beta",
23
+ "@pie-lib/render-ui": "^6.1.0",
24
24
  "@tiptap/core": "3.20.0",
25
25
  "@tiptap/extension-character-count": "3.20.0",
26
26
  "@tiptap/extension-color": "3.20.0",
@@ -59,6 +59,6 @@
59
59
  "peerDependencies": {
60
60
  "react": "^18.2.0"
61
61
  },
62
- "gitHead": "71d88b032a54ee9ffca51add8c0341a9e1ef780f",
62
+ "gitHead": "433bcb776c2975739e7417f133d31cd996164116",
63
63
  "scripts": {}
64
64
  }
@@ -243,7 +243,7 @@ export const EditableHtml = (props) => {
243
243
  let cb;
244
244
 
245
245
  if (scheduled && result) {
246
- // finish editing only on success
246
+ // finish editing only on success
247
247
  cb = props.onChange;
248
248
  }
249
249
 
@@ -424,10 +424,12 @@ const StyledEditorContent = styled(EditorContent, {
424
424
  },
425
425
 
426
426
  // Out of flow so the caret stays at the start of the block; in-flow ::before pushes the caret after the hint text.
427
- '& p.is-editor-empty, & div.is-editor-empty': {
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': {
428
430
  position: 'relative',
429
431
  },
430
- '& p.is-editor-empty::before, & div.is-editor-empty::before': {
432
+ '& p[data-placeholder].is-empty:only-child::before, & div[data-placeholder].is-empty:only-child::before': {
431
433
  content: 'attr(data-placeholder)',
432
434
  position: 'absolute',
433
435
  left: 0,
@@ -0,0 +1,72 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import DragDropChoice from '../choice';
4
+
5
+ // Mock @dnd-kit hooks to avoid DndContext requirement
6
+ jest.mock('@dnd-kit/core', () => ({
7
+ useDraggable: jest.fn(() => ({
8
+ attributes: {},
9
+ listeners: {},
10
+ setNodeRef: jest.fn(),
11
+ isDragging: false,
12
+ })),
13
+ useDroppable: jest.fn(() => ({
14
+ setNodeRef: jest.fn(),
15
+ isOver: false,
16
+ active: null,
17
+ })),
18
+ }));
19
+
20
+ jest.mock('@pie-lib/math-rendering', () => ({
21
+ renderMath: jest.fn(),
22
+ }));
23
+
24
+ // Collect the CSS rules emotion/MUI injected into the document (jsdom uses insertRule).
25
+ const collectEmotionRules = () => {
26
+ const rules = [];
27
+ for (const sheet of Array.from(document.styleSheets)) {
28
+ try {
29
+ for (const rule of Array.from(sheet.cssRules)) {
30
+ rules.push(rule.cssText);
31
+ }
32
+ } catch (e) {
33
+ /* inaccessible stylesheet */
34
+ }
35
+ }
36
+ return rules;
37
+ };
38
+
39
+ describe('DragInTheBlank choice', () => {
40
+ const defaultProps = {
41
+ value: { id: '1', value: '<math>1/2</math>' },
42
+ disabled: false,
43
+ instanceId: 'test-instance',
44
+ n: { index: 0 },
45
+ onChange: jest.fn(),
46
+ removeResponse: jest.fn(),
47
+ duplicates: false,
48
+ };
49
+
50
+ it('renders without crashing', () => {
51
+ const { container } = render(<DragDropChoice {...defaultProps} />);
52
+ expect(container.firstChild).toBeInTheDocument();
53
+ });
54
+
55
+ describe('fraction math styling', () => {
56
+ it('enlarges numerator/denominator digits adjacent to a fraction to 120%', () => {
57
+ render(<DragDropChoice {...defaultProps} />);
58
+ const rule = collectEmotionRules().find((r) => r.includes('mjx-mn') && r.includes('mjx-mfrac'));
59
+ expect(rule).toBeDefined();
60
+ expect(rule).toMatch(/mjx-mn:has\(~\s*mjx-mfrac\)/);
61
+ expect(rule).toMatch(/mjx-mfrac\s*~\s*mjx-mn/);
62
+ expect(rule).toMatch(/font-size:\s*120%\s*!important/i);
63
+ });
64
+
65
+ it('keeps the existing mjx-frac 120% rule', () => {
66
+ render(<DragDropChoice {...defaultProps} />);
67
+ const rule = collectEmotionRules().find((r) => /(^|[^-])mjx-frac/.test(r) && !r.includes('mjx-mfrac'));
68
+ expect(rule).toBeDefined();
69
+ expect(rule).toMatch(/font-size:\s*120%\s*!important/i);
70
+ });
71
+ });
72
+ });
@@ -10,6 +10,9 @@ import { GripIcon } from '../../icons/RespArea';
10
10
 
11
11
  const StyledContent = styled('span')(({ theme }) => ({
12
12
  border: `solid 0px ${theme.palette.primary.main}`,
13
+ '& mjx-mn:has(~ mjx-mfrac), mjx-mfrac ~ mjx-mn': {
14
+ fontSize: '120% !important',
15
+ },
13
16
  '& mjx-frac': {
14
17
  fontSize: '120% !important',
15
18
  },
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { NodeViewWrapper } from '@tiptap/react';
4
- import { mq } from '@pie-lib/math-input';
4
+ import { mq } from '@pie-lib/math-input-mathlive';
5
5
  import { styled } from '@mui/material/styles';
6
6
 
7
7
  const StyledSpanContainer = styled('span')(() => ({
@@ -11,7 +11,7 @@ jest.mock('@tiptap/react', () => ({
11
11
  ),
12
12
  }));
13
13
 
14
- jest.mock('@pie-lib/math-input', () => ({
14
+ jest.mock('@pie-lib/math-input-mathlive', () => ({
15
15
  mq: {
16
16
  Static: ({ latex }) => (
17
17
  <div data-testid="mq-static" data-latex={latex}>
@@ -39,6 +39,7 @@ describe('ImageComponent', () => {
39
39
 
40
40
  const createMockEditor = (selection = { from: 0, to: 1 }) => ({
41
41
  _tiptapContainerEl: document.body,
42
+ isEditable: true,
42
43
  commands: {
43
44
  updateAttributes: jest.fn(),
44
45
  focus: jest.fn(),
@@ -264,16 +264,16 @@ describe('ResponseAreaExtension', () => {
264
264
  jest.resetModules();
265
265
  });
266
266
 
267
- const buildInsertCommand = () => {
267
+ const buildInsertCommand = (type = 'inline-dropdown') => {
268
268
  const { ResponseAreaExtension } = require('../responseArea');
269
269
  const context = {
270
270
  options: {
271
- type: 'inline-dropdown',
271
+ type,
272
272
  maxResponseAreas: 5,
273
273
  },
274
274
  };
275
275
  const commands = ResponseAreaExtension.addCommands.call(context);
276
- return commands.insertResponseArea('inline-dropdown');
276
+ return commands.insertResponseArea(type);
277
277
  };
278
278
 
279
279
  const createDoc = (existingCount, typeName = 'inline_dropdown') => ({
@@ -391,6 +391,60 @@ describe('ResponseAreaExtension', () => {
391
391
  expect(create).not.toHaveBeenCalled();
392
392
  expect(mockTr.insert).not.toHaveBeenCalled();
393
393
  });
394
+
395
+ describe('selection after insert', () => {
396
+ const buildStateAndTr = (typeName, { withResolve = false } = {}) => {
397
+ const mockInlineNode = { nodeSize: 1 };
398
+ const create = jest.fn(() => mockInlineNode);
399
+ const mockDoc = {
400
+ descendants: jest.fn(),
401
+ content: { size: 50 },
402
+ ...(withResolve ? { resolve: jest.fn((pos) => ({ resolved: pos })) } : {}),
403
+ };
404
+ const mockTr = {
405
+ insert: jest.fn(),
406
+ doc: mockDoc,
407
+ setSelection: jest.fn(),
408
+ };
409
+ const state = {
410
+ schema: { nodes: { [typeName]: { create } } },
411
+ doc: mockDoc,
412
+ selection: { from: 5 },
413
+ };
414
+
415
+ return { mockTr, state };
416
+ };
417
+
418
+ it.each(['inline-dropdown', 'explicit-constructed-response'])(
419
+ 'sets a NodeSelection on the inserted node for %s',
420
+ (type) => {
421
+ const insert = buildInsertCommand(type);
422
+ const typeName = type.replace(/-/g, '_');
423
+ const { mockTr, state } = buildStateAndTr(typeName);
424
+ const { NodeSelection, TextSelection } = require('prosemirror-state');
425
+
426
+ insert({ tr: mockTr, state, dispatch: jest.fn(), commands: { focus: jest.fn() } });
427
+
428
+ expect(NodeSelection.create).toHaveBeenCalledWith(mockTr.doc, 5);
429
+ expect(TextSelection.near).not.toHaveBeenCalled();
430
+ expect(mockTr.setSelection).toHaveBeenCalledWith({ type: 'node', pos: 5 });
431
+ },
432
+ );
433
+
434
+ it('sets a TextSelection just after the inserted node for math-templated (no NodeSelection)', () => {
435
+ const insert = buildInsertCommand('math-templated');
436
+ const { mockTr, state } = buildStateAndTr('math_templated', { withResolve: true });
437
+ const { NodeSelection, TextSelection } = require('prosemirror-state');
438
+
439
+ insert({ tr: mockTr, state, dispatch: jest.fn(), commands: { focus: jest.fn() } });
440
+
441
+ expect(NodeSelection.create).not.toHaveBeenCalled();
442
+ // usedPos (5) + nodeSize (1) === 6
443
+ expect(mockTr.doc.resolve).toHaveBeenCalledWith(6);
444
+ expect(TextSelection.near).toHaveBeenCalledWith({ resolved: 6 }, 1);
445
+ expect(mockTr.setSelection).toHaveBeenCalledWith({ type: 'text', pos: { resolved: 6 }, dir: 1 });
446
+ });
447
+ });
394
448
  });
395
449
  });
396
450
  });
@@ -125,7 +125,7 @@ function ImageComponent(props) {
125
125
  );
126
126
 
127
127
  const applySizeData = useCallback(() => {
128
- if (!node.attrs.width || !imgRef.current) return;
128
+ if (!node.attrs.width || !imgRef.current || !imgRef.current.naturalWidth) return;
129
129
  const resizePercent = getPercentFromWidth(node.attrs.width);
130
130
  if (node.attrs.resizePercent === resizePercent) return;
131
131
  updateThisNode({ resizePercent });
@@ -138,7 +138,7 @@ function ImageComponent(props) {
138
138
 
139
139
  useEffect(() => {
140
140
  if (selected) {
141
- if (onlyThisNodeSelected) {
141
+ if (onlyThisNodeSelected && editor.isEditable) {
142
142
  // Only open the upload UI for a fresh placeholder. Remounting after tab switch
143
143
  // would otherwise call insertImageRequested again and reopen the file modal.
144
144
  const hasImageSrc = String(node.attrs?.src ?? '').trim();
@@ -156,15 +156,17 @@ function ImageComponent(props) {
156
156
  } else {
157
157
  setShowToolbar(selected);
158
158
  }
159
- }, [onlyThisNodeSelected, selected]);
159
+ }, [onlyThisNodeSelected, selected, editor.isEditable]);
160
160
 
161
161
  useEffect(() => {
162
162
  applySizeData();
163
163
 
164
- const resizeHandle = resizeRef.current;
165
- if (resizeHandle) {
164
+ const resizeHandle = resizeRef?.current;
165
+
166
+ if (resizeHandle && editor.isEditable) {
166
167
  resizeHandle.addEventListener('mousedown', initResize, false);
167
168
  }
169
+
168
170
  return () => {
169
171
  if (resizeHandle) {
170
172
  resizeHandle.removeEventListener('mousedown', initResize, false);
@@ -272,7 +274,7 @@ function ImageComponent(props) {
272
274
  onLoad={loadImage}
273
275
  alt={node.attrs.alt}
274
276
  />
275
- <StyledResize ref={resizeRef} className="resize" />
277
+ <StyledResize ref={resizeRef} className="resize" style={{ display: editor.isEditable ? undefined : 'none' }} />
276
278
  </StyledImageContainer>
277
279
  </StyledRoot>
278
280
 
@@ -195,7 +195,13 @@ export const ResponseAreaExtension = Extension.create({
195
195
  // tr.setSelection(NodeSelection.create(tr.doc, usedPos))
196
196
 
197
197
  // --- Cursor move behavior for certain types (Slate: moveFocusTo next text) ---
198
- if (['math_templated', 'inline_dropdown', 'explicit_constructed_response'].includes(typeName)) {
198
+ // Only types whose node view opens its own toolbar on selection
199
+ // (inline_dropdown, explicit_constructed_response) benefit from a
200
+ // NodeSelection here — the toolbar is the visual feedback that it's
201
+ // selected. math_templated has no such UI, so a NodeSelection there
202
+ // just looks unfocused (a highlighted node, no blinking caret);
203
+ // moving the cursor to just after it gives a real, visible caret.
204
+ if (['inline_dropdown', 'explicit_constructed_response'].includes(typeName)) {
199
205
  tr.setSelection(NodeSelection.create(tr.doc, usedPos));
200
206
  } else {
201
207
  const after = usedPos + newInline.nodeSize;
package/LICENSE.md DELETED
@@ -1,5 +0,0 @@
1
- Copyright 2019 CoreSpring Inc
2
-
3
- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
4
-
5
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.