diff --git a/packages/inline-dropdown/configure/src/__tests__/inline-dropdown-toolbar.test.jsx b/packages/inline-dropdown/configure/src/__tests__/inline-dropdown-toolbar.test.jsx index ee1e5af328..4f2ccf6142 100644 --- a/packages/inline-dropdown/configure/src/__tests__/inline-dropdown-toolbar.test.jsx +++ b/packages/inline-dropdown/configure/src/__tests__/inline-dropdown-toolbar.test.jsx @@ -8,11 +8,7 @@ import RespAreaToolbar from '../inline-dropdown-toolbar'; jest.mock('@pie-lib/editable-html-tip-tap', () => { return function MockEditableHtml(props) { return ( -
+
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}