props.onChange(e.target.value)}
@@ -205,21 +201,10 @@ describe('RespAreaToolbar', () => {
}
}
- const { rerender } = render(
-
- );
+ const { rerender } = render(
);
// Trigger an update by rerendering with new props
- rerender(
-
- );
+ rerender(
);
expect(renderMath).toHaveBeenCalled();
});
@@ -315,7 +300,6 @@ describe('RespAreaToolbar', () => {
instance.onDone(markup);
expect(localEditor.commands.updateAttributes).toHaveBeenCalledWith('inline_dropdown', { value: markup });
- expect(localOnToolbarDone).toHaveBeenCalledWith(false);
});
it('should call onAddChoice when editing a choice', () => {
@@ -384,7 +368,7 @@ describe('RespAreaToolbar', () => {
expect(localEditor.commands.updateAttributes).toHaveBeenCalledWith('inline_dropdown', { value: newValue });
});
- it('should call onToolbarDone and onSelectChoice', () => {
+ it('should call onSelectChoice without closing the toolbar', () => {
const localEditor = {
...editor,
commands: {
@@ -404,7 +388,7 @@ describe('RespAreaToolbar', () => {
instance.onSelectChoice(newValue, index);
- expect(localOnToolbarDone).toHaveBeenCalledWith(false);
+ expect(localOnToolbarDone).not.toHaveBeenCalled();
expect(localOnSelectChoice).toHaveBeenCalledWith(index);
});
@@ -452,7 +436,7 @@ describe('RespAreaToolbar', () => {
instance.onRemoveChoice(value, index);
expect(localEditor.commands.updateAttributes).toHaveBeenCalledWith('inline_dropdown', { value: null });
- expect(localOnToolbarDone).toHaveBeenCalledWith(false);
+ expect(localOnToolbarDone).not.toHaveBeenCalled();
});
it('should call onRemoveChoice for non-selected choice', () => {
@@ -547,6 +531,15 @@ describe('RespAreaToolbar', () => {
expect(instance.preventDone).toBe(true);
});
+ it('should set clickedInside so blur from opening edit does not commit', () => {
+ const instance = createInstance();
+
+ instance.clickedInside = false;
+ instance.onEditChoice('dog', 1);
+
+ expect(instance.clickedInside).toBe(true);
+ });
+
it('should save current edit before starting a new edit', () => {
const localOnAddChoice = jest.fn();
const localEditor = {
@@ -690,125 +683,91 @@ describe('RespAreaToolbar', () => {
instance.onBlur();
expect(instance.clickedInside).toBe(false);
- expect(onCheck).not.toHaveBeenCalled();
+ expect(onToolbarDone).not.toHaveBeenCalled();
});
- it('should call onCheck if no choices exist', () => {
+ it('should commit pending choice on blur without closing the toolbar', () => {
const mockTr = { isDone: false, deleteSelection: jest.fn() };
+ const mockDispatch = jest.fn();
const localEditor = {
...editor,
state: {
...editor.state,
tr: mockTr,
},
+ view: {
+ ...editor.view,
+ dispatch: mockDispatch,
+ },
};
- const localOnCheck = jest.fn();
+ const localOnToolbarDone = jest.fn();
const instance = createInstance({
choices: null,
editor: localEditor,
- onCheck: localOnCheck,
+ onToolbarDone: localOnToolbarDone,
});
instance.onBlur();
- expect(localOnCheck).toHaveBeenCalled();
+ expect(mockDispatch).toHaveBeenCalled();
+ expect(localOnToolbarDone).not.toHaveBeenCalled();
});
- it('should call onCheck if less than 2 choices', () => {
- const mockTr = { isDone: false, deleteSelection: jest.fn() };
+ it('should save the choice being edited when blurring with no relatedTarget', () => {
+ const localOnAddChoice = jest.fn();
const localEditor = {
...editor,
- state: {
- ...editor.state,
- tr: mockTr,
+ commands: {
+ updateAttributes: jest.fn(),
+ refreshResponseArea: jest.fn(),
},
};
- const localOnCheck = jest.fn();
const instance = createInstance({
- choices: [{ label: 'cow', correct: true }],
editor: localEditor,
- onCheck: localOnCheck,
+ onAddChoice: localOnAddChoice,
});
- instance.onBlur();
-
- expect(localOnCheck).toHaveBeenCalled();
- });
-
- it('should call onCheck if no correct response', () => {
- const mockTr = { isDone: false, deleteSelection: jest.fn() };
- const localEditor = {
- ...editor,
- state: {
- ...editor.state,
- tr: mockTr,
- },
- };
- const localOnCheck = jest.fn();
- const instance = createInstance({
- choices: [
- { label: 'cow', correct: false },
- { label: 'dog', correct: false },
- ],
- editor: localEditor,
- onCheck: localOnCheck,
- });
+ instance.editorRef = { getHTML: () => '
updated label
' };
+ instance.state.editedChoiceIndex = 1;
+ instance.preventDone = true;
instance.onBlur();
- expect(localOnCheck).toHaveBeenCalled();
+ expect(localOnAddChoice).toHaveBeenCalledWith('0', '
updated label
', 1);
+ expect(instance.state.editedChoiceIndex).toBe(-1);
+ expect(instance.preventDone).toBe(false);
});
+ });
- it('should not call onCheck if valid choices exist', () => {
- const mockTr = { isDone: false, deleteSelection: jest.fn() };
+ describe('componentWillUnmount', () => {
+ it('should save the choice being edited when the toolbar unmounts', () => {
+ const localOnAddChoice = jest.fn();
const localEditor = {
...editor,
- state: {
- ...editor.state,
- tr: mockTr,
+ commands: {
+ updateAttributes: jest.fn(),
+ refreshResponseArea: jest.fn(),
},
};
- const localOnCheck = jest.fn();
- const instance = createInstance({
- editor: localEditor,
- onCheck: localOnCheck,
- });
- instance.clickedInside = false;
+ const instance = createInstance({ onAddChoice: localOnAddChoice, editor: localEditor });
- instance.onBlur();
+ instance.editorRef = { getHTML: () => '
saved on close
' };
+ instance.state.editedChoiceIndex = 0;
+
+ instance.componentWillUnmount();
- expect(localOnCheck).not.toHaveBeenCalled();
+ expect(localOnAddChoice).toHaveBeenCalledWith('0', '
saved on close
', 0);
+ expect(instance.state.editedChoiceIndex).toBe(-1);
});
- it('should execute callback from onCheck', () => {
- const mockTr = { isDone: false, deleteSelection: jest.fn() };
- const mockDispatch = jest.fn();
- const localEditor = {
- ...editor,
- state: {
- ...editor.state,
- tr: mockTr,
- },
- view: {
- ...editor.view,
- dispatch: mockDispatch,
- },
- };
- const localOnCheck = jest.fn();
- const localOnToolbarDone = jest.fn();
- const instance = createInstance({
- choices: null,
- editor: localEditor,
- onCheck: localOnCheck,
- onToolbarDone: localOnToolbarDone,
- });
- localOnCheck.mockImplementation((callback) => callback());
+ it('should not call onAddChoice when not editing a choice', () => {
+ const localOnAddChoice = jest.fn();
+ const instance = createInstance({ onAddChoice: localOnAddChoice });
- instance.onBlur();
+ instance.state.editedChoiceIndex = -1;
+ instance.componentWillUnmount();
- expect(mockTr.deleteSelection).toHaveBeenCalled();
- expect(mockDispatch).toHaveBeenCalled();
- expect(localOnToolbarDone).toHaveBeenCalledWith(false);
+ expect(localOnAddChoice).not.toHaveBeenCalled();
});
});
@@ -951,7 +910,8 @@ describe('RespAreaToolbar', () => {
// Mock editor with HTML content
instance.editorRef = {
- getHTML: jest.fn()
+ getHTML: jest
+ .fn()
.mockReturnValueOnce('
modified dog
')
.mockReturnValueOnce('
modified cat
'),
};
@@ -1000,8 +960,9 @@ describe('RespAreaToolbar', () => {
instance.onEditChoice('dog', 1);
// Should have auto-saved and updated attributes since it was the correct choice
- expect(localEditor.commands.updateAttributes).toHaveBeenCalledWith('inline_dropdown', { value: '
modified cow
' });
- expect(localOnToolbarDone).toHaveBeenCalledWith(false);
+ expect(localEditor.commands.updateAttributes).toHaveBeenCalledWith('inline_dropdown', {
+ value: '
modified cow
',
+ });
expect(instance.state.editedChoiceIndex).toBe(1);
});
});
@@ -1072,23 +1033,29 @@ describe('RespAreaToolbar', () => {
it('should handle empty choices array', () => {
const mockTr = { isDone: false, deleteSelection: jest.fn() };
+ const mockDispatch = jest.fn();
const localEditor = {
...editor,
state: {
...editor.state,
tr: mockTr,
},
+ view: {
+ ...editor.view,
+ dispatch: mockDispatch,
+ },
};
- const localOnCheck = jest.fn();
+ const localOnToolbarDone = jest.fn();
const instance = createInstance({
choices: [],
editor: localEditor,
- onCheck: localOnCheck,
+ onToolbarDone: localOnToolbarDone,
});
instance.onBlur();
- expect(localOnCheck).toHaveBeenCalled();
+ expect(mockDispatch).toHaveBeenCalled();
+ expect(localOnToolbarDone).not.toHaveBeenCalled();
});
});
@@ -1250,9 +1217,7 @@ describe('MenuItem Integration Tests', () => {
});
it('should render choice labels as HTML', () => {
- const choices = [
- { label: '
Bold choice
', correct: true },
- ];
+ const choices = [{ label: '
Bold choice
', correct: true }];
const instance = createToolbar(choices);
const rendered = render(<>{instance.render()}>);
@@ -1363,7 +1328,7 @@ describe('MenuItem Integration Tests', () => {
fireEvent.click(removeButtons[0]); // Remove 'cow' which is the selected value
expect(editor.commands.updateAttributes).toHaveBeenCalledWith('inline_dropdown', { value: null });
- expect(onToolbarDone).toHaveBeenCalledWith(false);
+ expect(onToolbarDone).not.toHaveBeenCalled();
});
});
@@ -1386,9 +1351,7 @@ describe('MenuItem Integration Tests', () => {
});
it('should not trigger choice selection when clicking action buttons', () => {
- const choices = [
- { label: 'cow', correct: true },
- ];
+ const choices = [{ label: 'cow', correct: true }];
const instance = createToolbar(choices);
const rendered = render(<>{instance.render()}>);
@@ -1417,9 +1380,7 @@ describe('MenuItem Integration Tests', () => {
});
it('should show check icon for correct choice', () => {
- const choices = [
- { label: 'correct answer', correct: true },
- ];
+ const choices = [{ label: 'correct answer', correct: true }];
const instance = createToolbar(choices);
const rendered = render(<>{instance.render()}>);
@@ -1432,9 +1393,7 @@ describe('MenuItem Integration Tests', () => {
describe('MenuItem Edge Cases', () => {
it('should handle empty label gracefully', () => {
- const choices = [
- { label: '', correct: false },
- ];
+ const choices = [{ label: '', correct: false }];
const instance = createToolbar(choices);
const rendered = render(<>{instance.render()}>);
@@ -1444,9 +1403,7 @@ describe('MenuItem Integration Tests', () => {
});
it('should handle HTML entities in labels', () => {
- const choices = [
- { label: '<p>escaped</p>', correct: false },
- ];
+ const choices = [{ label: '<p>escaped</p>', correct: false }];
const instance = createToolbar(choices);
const rendered = render(<>{instance.render()}>);
@@ -1456,9 +1413,7 @@ describe('MenuItem Integration Tests', () => {
it('should handle very long labels', () => {
const longLabel = '
' + 'a'.repeat(500) + '
';
- const choices = [
- { label: longLabel, correct: false },
- ];
+ const choices = [{ label: longLabel, correct: false }];
const instance = createToolbar(choices);
const rendered = render(<>{instance.render()}>);
@@ -1467,9 +1422,7 @@ describe('MenuItem Integration Tests', () => {
});
it('should handle special characters in labels', () => {
- const choices = [
- { label: '
Math: x² + y² = z²
', correct: false },
- ];
+ const choices = [{ label: '
Math: x² + y² = z²
', correct: false }];
const instance = createToolbar(choices);
const rendered = render(<>{instance.render()}>);
diff --git a/packages/inline-dropdown/configure/src/__tests__/main.test.jsx b/packages/inline-dropdown/configure/src/__tests__/main.test.jsx
index 499d276e69..265d5deb12 100644
--- a/packages/inline-dropdown/configure/src/__tests__/main.test.jsx
+++ b/packages/inline-dropdown/configure/src/__tests__/main.test.jsx
@@ -1,11 +1,21 @@
import { render } from '@testing-library/react';
import React from 'react';
+import { deleteInlineDropdownByIndex } from '@pie-lib/editable-html-tip-tap';
import { Main } from '../main';
import { cloneDeep } from 'lodash-es';
import sensibleDefaults from '../defaults';
import { createSlateMarkup, processMarkup } from '../markupUtils';
+jest.mock('@pie-lib/editable-html-tip-tap', () => {
+ const actual = jest.requireActual('@pie-lib/editable-html-tip-tap');
+
+ return {
+ ...actual,
+ deleteInlineDropdownByIndex: jest.fn(),
+ };
+});
+
jest.mock('@pie-lib/config-ui', () => ({
choiceUtils: {
firstAvailableIndex: jest.fn(),
@@ -144,8 +154,12 @@ describe('Main', () => {
const instance = new Main(defaults);
// Mock setState to execute updates immediately for testing
- instance.setState = jest.fn((state) => {
+ instance.setState = jest.fn((state, callback) => {
Object.assign(instance.state, typeof state === 'function' ? state(instance.state) : state);
+
+ if (callback) {
+ callback();
+ }
});
return instance;
@@ -217,7 +231,8 @@ describe('Main', () => {
});
describe('onChange', () => {
- it('Removing choices (keeping only 1 choice): slateMarkup and choices are updated', () => {
+ it('Removing choices (keeping only 1 choice): syncs markup and choices without discarding', () => {
+ const oldModel = w.props.model;
const newChoices = {
...model.choices,
0: model.choices['0'].slice(0, 1),
@@ -227,11 +242,15 @@ describe('Main', () => {
w.setState({ respAreaChoices: newChoices });
w.onChange(newMarkup);
- // TODO do we have to test what happens if clicking on OK/Cancel? How?
- expect(onModelChanged).not.toBeCalled();
+ expect(onModelChanged).toBeCalledWith({
+ ...oldModel,
+ slateMarkup: newMarkup,
+ choices: newChoices,
+ });
});
- it('No correct choice selected: slateMarkup and choices are updated', () => {
+ it('No correct choice selected: syncs markup and choices without discarding', () => {
+ const oldModel = w.props.model;
const newChoices = {
...model.choices,
0: [
@@ -257,8 +276,11 @@ describe('Main', () => {
w.setState({ respAreaChoices: newChoices });
w.onChange(newMarkup);
- // TODO do we have to test what happens if clicking on OK/Cancel? How?
- expect(onModelChanged).not.toBeCalled();
+ expect(onModelChanged).toBeCalledWith({
+ ...oldModel,
+ slateMarkup: newMarkup,
+ choices: newChoices,
+ });
});
it('New choice: slateMarkup and choices are updated', () => {
@@ -338,6 +360,70 @@ describe('Main', () => {
});
});
+ describe('validateResponseAreaClose', () => {
+ const node = { attrs: { index: 0 } };
+ const closeToolbar = jest.fn();
+ const onCancel = jest.fn();
+
+ beforeEach(() => {
+ w.setState({ respAreaChoices: cloneDeep(model.choices) });
+ closeToolbar.mockClear();
+ onCancel.mockClear();
+ deleteInlineDropdownByIndex.mockClear();
+ });
+
+ it('closes toolbar when choices are valid', () => {
+ w.validateResponseAreaClose(node, 5, {}, closeToolbar, onCancel);
+
+ expect(closeToolbar).toHaveBeenCalled();
+ expect(w.state.warning.open).toBe(false);
+ expect(deleteInlineDropdownByIndex).not.toHaveBeenCalled();
+ });
+
+ it('shows warning when choices are invalid', () => {
+ w.setState({
+ respAreaChoices: {
+ 0: [{ label: 'only', value: '0', correct: false }],
+ },
+ });
+
+ w.validateResponseAreaClose(node, 5, {}, closeToolbar, onCancel);
+
+ expect(closeToolbar).not.toHaveBeenCalled();
+ expect(w.state.warning.open).toBe(true);
+ });
+
+ it('deletes response area and closes toolbar on confirm', () => {
+ const editor = {};
+
+ w.setState({
+ respAreaChoices: {
+ 0: [{ label: 'only', value: '0', correct: false }],
+ },
+ });
+
+ w.validateResponseAreaClose(node, 5, editor, closeToolbar, onCancel);
+ w.state.warning.onConfirm();
+
+ expect(deleteInlineDropdownByIndex).toHaveBeenCalledWith(editor, 0, 5);
+ expect(closeToolbar).toHaveBeenCalled();
+ });
+
+ it('calls pie-lib onCancel when dialog is dismissed', () => {
+ w.setState({
+ respAreaChoices: {
+ 0: [{ label: 'only', value: '0', correct: false }],
+ },
+ });
+
+ w.validateResponseAreaClose(node, 5, {}, closeToolbar, onCancel);
+ w.state.warning.onClose();
+
+ expect(onCancel).toHaveBeenCalled();
+ expect(deleteInlineDropdownByIndex).not.toHaveBeenCalled();
+ });
+ });
+
describe('onChoiceRationaleChanged', () => {
it('changes the choice level rationale value', () => {
w.onChoiceRationaleChanged(0, {
diff --git a/packages/inline-dropdown/configure/src/inline-dropdown-toolbar.jsx b/packages/inline-dropdown/configure/src/inline-dropdown-toolbar.jsx
index 1086263993..48bffb2987 100644
--- a/packages/inline-dropdown/configure/src/inline-dropdown-toolbar.jsx
+++ b/packages/inline-dropdown/configure/src/inline-dropdown-toolbar.jsx
@@ -181,7 +181,6 @@ class RespAreaToolbar extends React.Component {
onDone: PropTypes.func,
choices: PropTypes.array,
onAddChoice: PropTypes.func.isRequired,
- onCheck: PropTypes.func,
editorCallback: PropTypes.func,
onRemoveChoice: PropTypes.func.isRequired,
onSelectChoice: PropTypes.func.isRequired,
@@ -227,6 +226,21 @@ class RespAreaToolbar extends React.Component {
renderMath(domNode);
}
+ componentWillUnmount() {
+ this.commitPendingEdit();
+ }
+
+ commitPendingEdit = () => {
+ const { editedChoiceIndex } = this.state;
+
+ if (editedChoiceIndex < 0 || !this.editorRef?.getHTML) {
+ return;
+ }
+
+ this.preventDone = false;
+ this.onDone(this.editorRef.getHTML() || '');
+ };
+
onRespAreaChange = (respAreaMarkup) => {
this.setState({ respAreaMarkup });
};
@@ -247,7 +261,6 @@ class RespAreaToolbar extends React.Component {
if (editedChoiceIndex >= 0 && choices?.[editedChoiceIndex]?.correct) {
editor.commands.updateAttributes('inline_dropdown', { value: val });
- onToolbarDone(false);
}
if (!isEmpty(onlyText)) {
@@ -263,7 +276,6 @@ class RespAreaToolbar extends React.Component {
editor.commands.updateAttributes('inline_dropdown', { value: newValue });
- onToolbarDone(false);
onSelectChoice(index);
editor.commands.refreshResponseArea();
};
@@ -273,7 +285,6 @@ class RespAreaToolbar extends React.Component {
if (isEqual(val, node.attrs.value)) {
editor.commands.updateAttributes('inline_dropdown', { value: null });
- onToolbarDone(false);
}
onRemoveChoice(index);
@@ -283,6 +294,7 @@ class RespAreaToolbar extends React.Component {
onEditChoice = (val, index) => {
const { editedChoiceIndex } = this.state;
+ this.clickedInside = true;
this.preventDone = true;
if (editedChoiceIndex >= 0) {
@@ -315,19 +327,14 @@ class RespAreaToolbar extends React.Component {
return;
}
- const { node, choices, onCheck, onToolbarDone, editor } = this.props;
- const correctResponse = (choices || []).find((choice) => choice.correct);
-
- this.onAddChoice();
- if (!choices || (choices && choices.length < 2) || !correctResponse) {
- onCheck(() => {
- const { tr } = editor.state;
+ const { editedChoiceIndex } = this.state;
- tr.deleteSelection();
- editor.view.dispatch(tr);
- onToolbarDone(false);
- });
+ if (editedChoiceIndex >= 0) {
+ this.commitPendingEdit();
+ return;
}
+
+ this.onAddChoice();
};
onClickInside = () => {
@@ -351,6 +358,7 @@ class RespAreaToolbar extends React.Component {
return (
{
- if (!e.relatedTarget) {
- return;
- }
-
const isInInsertCharacter = !!(e.relatedTarget && e.relatedTarget.closest('.insert-character-dialog'));
const isInDoneButton = !!(e.relatedTarget && e.relatedTarget.closest('[aria-label="Done"]'));
- this.preventDone = isInInsertCharacter || isInDoneButton;
- if (isInInsertCharacter || isInDoneButton) {
- this.clickedInside = true;
+ if (e.relatedTarget) {
+ this.preventDone = isInInsertCharacter || isInDoneButton;
+
+ if (isInInsertCharacter || isInDoneButton) {
+ this.clickedInside = true;
+ }
}
- this.onBlur(e);
+
+ this.onBlur();
}}
placeholder="Add Choice"
pluginProps={getPluginProps(responseAreaInputConfiguration?.inputConfiguration, baseInputConfiguration)}
diff --git a/packages/inline-dropdown/configure/src/main.jsx b/packages/inline-dropdown/configure/src/main.jsx
index da457f6270..8b41bb6ce5 100644
--- a/packages/inline-dropdown/configure/src/main.jsx
+++ b/packages/inline-dropdown/configure/src/main.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
-import EditableHtml, { ALL_PLUGINS } from '@pie-lib/editable-html-tip-tap';
+import EditableHtml, { ALL_PLUGINS, deleteInlineDropdownByIndex } from '@pie-lib/editable-html-tip-tap';
import { AlertDialog, InputContainer, layout, settings } from '@pie-lib/config-ui';
import { renderMath } from '@pie-lib/math-rendering';
import { color } from '@pie-lib/render-ui';
@@ -169,21 +169,44 @@ export class Main extends React.Component {
this.onModelChange({ slateMarkup });
};
- onCheck = (callback) => {
+ onCheck = (confirmCallback, cancelCallback) => {
this.setState({
warning: {
open: true,
text: 'Response areas with under 2 options or with no correct answers will be discarded.',
onClose: () => {
- this.setState({ warning: { open: false } });
+ this.setState({ warning: { open: false } }, cancelCallback);
},
onConfirm: () => {
- this.setState({ warning: { open: false } }, callback);
+ this.setState({ warning: { open: false } }, confirmCallback);
},
},
});
};
+ validateResponseAreaClose = (node, pos, editor, closeToolbar, onCancel) => {
+ const { respAreaChoices } = this.state;
+ const index = node.attrs.index;
+ const currentChoices = respAreaChoices[index] || [];
+ const shouldWarn = currentChoices.length < 2 || !currentChoices.find((c) => c.correct);
+
+ if (!shouldWarn) {
+ closeToolbar();
+ return;
+ }
+
+ editor._holdInlineDropdownToolbarIndex = index;
+
+ this.onCheck(
+ () => {
+ delete editor._holdInlineDropdownToolbarIndex;
+ deleteInlineDropdownByIndex(editor, index, pos);
+ closeToolbar();
+ },
+ onCancel,
+ );
+ };
+
onChange = (markup) => {
const { respAreaChoices } = this.state;
const domMarkup = createElementFromHTML(markup);
@@ -207,33 +230,21 @@ export class Main extends React.Component {
);
const newRespAreaChoices = {};
- let shouldWarn = false;
allRespAreas.forEach((el, index) => {
- const newChoices = existingRespAreaChoices[el.dataset.index] || [];
-
- if (newChoices.length < 2 || !newChoices.find((c) => c.correct)) {
- el.remove();
- shouldWarn = true;
- } else {
- newRespAreaChoices[index] = existingRespAreaChoices[el.dataset.index] || [];
- el.dataset.index = index;
+ const choices = existingRespAreaChoices[el.dataset.index];
+
+ if (choices) {
+ newRespAreaChoices[index] = choices;
}
+
+ el.dataset.index = index;
});
- if (shouldWarn) {
- this.onCheck(() =>
- this.onModelChange({
- choices: cloneDeep(newRespAreaChoices),
- slateMarkup: domMarkup.innerHTML,
- }),
- );
- } else {
- this.onModelChange({
- choices: cloneDeep(newRespAreaChoices),
- slateMarkup: domMarkup.innerHTML,
- });
- }
+ this.onModelChange({
+ choices: cloneDeep(newRespAreaChoices),
+ slateMarkup: domMarkup.innerHTML,
+ });
};
onAddChoice = (index, label, choiceIndex) => {
@@ -520,21 +531,23 @@ export class Main extends React.Component {
duplicates: true,
},
maxResponseAreas: maxResponseAreas,
- respAreaToolbar: (nodeInfo, editor, onToolbarDone) => {
+ onToolbarCloseRequest: ([node, pos], editor, closeToolbar, onCancel) => {
+ this.validateResponseAreaClose(node, pos, editor, closeToolbar, onCancel);
+ },
+ respAreaToolbar: (nodeInfo, editor, closeToolbar) => {
const [node, pos] = nodeInfo;
const { respAreaChoices } = this.state;
- return props => (
+ return (props) => (
this.onRemoveChoice(node.attrs.index, index)}
onSelectChoice={(index) => this.onSelectChoice(node.attrs.index, index)}
node={node}
pos={pos}
editor={editor}
- onToolbarDone={onToolbarDone}
+ onToolbarDone={closeToolbar}
choices={respAreaChoices[node.attrs.index]}
spellCheck={spellCheckEnabled}
uploadSoundSupport={uploadSoundSupport}