@pie-lib/editable-html 10.0.0-beta.2 → 10.0.0-beta.3

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.
@@ -1,27 +1,17 @@
1
1
  import React from 'react';
2
+ import { jsx } from 'slate-hyperscript';
2
3
  import debug from 'debug';
3
4
  import get from 'lodash/get';
4
- import { Data, Editor, Inline, Node as SlateNode } from 'slate';
5
+ import { Node as SlateNode, Editor } from 'slate';
5
6
 
6
7
  import Image from '@material-ui/icons/Image';
7
8
  import ImageComponent from './component';
8
9
  import ImageToolbar from './image-toolbar';
9
10
  import InsertImageHandler from './insert-image-handler';
11
+ import { ReactEditor } from 'slate-react';
10
12
 
11
13
  const log = debug('@pie-lib:editable-html:plugins:image');
12
14
 
13
- const getNodeBy = (editor, callback) => {
14
- const descendants = SlateNode.descendants(editor, {
15
- reverse: true
16
- });
17
-
18
- for (const [descendant, descendantPath] of descendants) {
19
- if (callback(descendant, descendantPath)) {
20
- return [descendant, descendantPath];
21
- }
22
- }
23
- };
24
-
25
15
  export default function ImagePlugin(opts) {
26
16
  const toolbar = opts.insertImageRequested && {
27
17
  icon: <Image />,
@@ -39,25 +29,31 @@ export default function ImagePlugin(opts) {
39
29
 
40
30
  editor.insertNode(inline);
41
31
 
42
- const [node, nodePath] = getNodeBy(
43
- editor,
44
- descendant => descendant.type === 'image' && get(descendant, 'data.newImage')
45
- );
32
+ // get the element just inserted
33
+ const [node, nodePath] = Editor.node(editor, editor.selection);
46
34
 
47
35
  opts.insertImageRequested(() => new InsertImageHandler(node, nodePath, editor));
48
36
  },
49
- customToolbar: (node, value, editor, onToolbarDone) => {
37
+ customToolbar: (node, nodePath, editor, onToolbarDone) => {
50
38
  const alignment = node.data.alignment;
51
39
  const alt = node.data.alt;
52
40
  const imageLoaded = node.data.loaded !== false;
53
41
  const onChange = newValues => {
54
42
  const update = {
55
- ...node.data.toObject(),
43
+ ...node.data,
56
44
  ...newValues
57
45
  };
58
46
 
59
- const change = value.change().setNodeByKey(node.key, { data: update });
60
- onToolbarDone(change, false);
47
+ editor.apply({
48
+ type: 'set_node',
49
+ path: nodePath,
50
+ properties: {
51
+ data: node.data
52
+ },
53
+ newProperties: { data: update }
54
+ });
55
+
56
+ onToolbarDone(null, false);
61
57
  };
62
58
 
63
59
  const Tb = () => (
@@ -85,28 +81,58 @@ export default function ImagePlugin(opts) {
85
81
  return editor;
86
82
  },
87
83
  supports: node => node.type === 'image',
88
- deleteNode: (e, node, value, onChange) => {
84
+ deleteNode: (e, node, nodePath, editor, onChange) => {
89
85
  e.preventDefault();
86
+
90
87
  if (opts.onDelete) {
91
- const update = node.data.merge(Data.create({ deleteStatus: 'pending' }));
88
+ const update = {
89
+ ...node.data,
90
+ deleteStatus: 'pending'
91
+ };
92
92
 
93
- let change = value.change().setNodeByKey(node.key, { data: update });
93
+ editor.apply({
94
+ type: 'set_node',
95
+ path: nodePath,
96
+ properties: {
97
+ data: node.data
98
+ },
99
+ newProperties: { data: update }
100
+ });
94
101
 
95
- onChange(change);
96
- opts.onDelete(node.data.get('src'), (err, v) => {
102
+ editor.selection = null;
103
+ onChange(editor);
104
+ opts.onDelete(node.data.src, (err) => {
97
105
  if (!err) {
98
- change = v.change().removeNodeByKey(node.key);
106
+ editor.apply({
107
+ type: 'remove_node',
108
+ path: nodePath
109
+ });
99
110
  } else {
100
111
  log('[error]: ', err);
101
- change = v
102
- .change()
103
- .setNodeByKey(node.key, node.data.merge(Data.create({ deleteStatus: 'failed' })));
112
+ editor.apply({
113
+ type: 'set_node',
114
+ path: nodePath,
115
+ properties: {
116
+ data: node.data
117
+ },
118
+ newProperties: { data: { ...node.data, deleteStatus: 'failed' } }
119
+ });
104
120
  }
105
- onChange(change);
121
+
122
+ editor.selection = null;
123
+ onChange(editor, () => {
124
+ setTimeout(() => ReactEditor.focus(editor), 50);
125
+ });
106
126
  });
107
127
  } else {
108
- let change = value.change().removeNodeByKey(node.key);
109
- onChange(change);
128
+ editor.selection = null;
129
+ editor.apply({
130
+ type: 'remove_node',
131
+ path: nodePath
132
+ });
133
+ onChange(editor, () => {
134
+ setTimeout(() => ReactEditor.focus(editor), 50);
135
+ });
110
136
  }
111
137
  },
112
138
  stopReset: value => {
@@ -114,7 +140,7 @@ export default function ImagePlugin(opts) {
114
140
  if (n.type !== 'image') {
115
141
  return;
116
142
  }
117
- return n.data.get('loaded') === false;
143
+ return n.data.loaded === false;
118
144
  });
119
145
  /** don't reset if there is an image pending insertion */
120
146
  return imgPendingInsertion !== undefined && imgPendingInsertion !== null;
@@ -180,10 +206,8 @@ export const serialization = {
180
206
  const width = parseInt(style.width.replace('px', ''), 10) || null;
181
207
  const height = parseInt(style.height.replace('px', ''), 10) || null;
182
208
 
183
- const out = {
184
- object: 'inline',
209
+ const out = jsx('element', {
185
210
  type: 'image',
186
- isVoid: true,
187
211
  data: {
188
212
  src: el.getAttribute('src'),
189
213
  width,
@@ -193,7 +217,7 @@ export const serialization = {
193
217
  alignment: el.getAttribute('alignment'),
194
218
  alt: el.getAttribute('alt')
195
219
  }
196
- };
220
+ });
197
221
  log('return object: ', out);
198
222
  return out;
199
223
  },
@@ -201,14 +225,17 @@ export const serialization = {
201
225
  if (object.type !== 'image') return;
202
226
 
203
227
  const { data } = object;
204
- const src = data.get('src');
205
- const width = data.get('width');
206
- const height = data.get('height');
207
- const alignment = data.get('alignment');
208
- const margin = data.get('margin');
209
- const justifyContent = data.get('margin');
210
- const alt = data.get('alt');
228
+ const {
229
+ alignment,
230
+ alt,
231
+ src,
232
+ height,
233
+ margin,
234
+ justifyContent,
235
+ width
236
+ } = data;
211
237
  const style = {};
238
+
212
239
  if (width) {
213
240
  style.width = `${width}px`;
214
241
  }
@@ -1,3 +1,4 @@
1
+ import { Editor } from 'slate';
1
2
  import { withHistory } from 'slate-history';
2
3
  import { withReact } from 'slate-react';
3
4
 
@@ -85,6 +86,11 @@ export const buildPlugins = (activePlugins, opts) => {
85
86
  };
86
87
 
87
88
  export const withPlugins = (editor, activePlugins) => {
89
+ editor.continueNormalization = () => {
90
+ Editor.setNormalizing(editor, true);
91
+ Editor.normalize(editor, { force: true });
92
+ };
93
+
88
94
  activePlugins.forEach(plugin => {
89
95
  if (typeof plugin.rules === 'function') {
90
96
  plugin.rules(editor);
@@ -25,7 +25,7 @@ function generateAdditionalKeys(keyData = []) {
25
25
 
26
26
  export const CustomToolbarComp = React.memo(
27
27
  props => {
28
- const { node, onFocus, onBlur, onClick, editor, nodePath } = props;
28
+ const { node, nodePath, onFocus, onBlur, onClick, editor } = props;
29
29
  const { pluginProps } = props || {};
30
30
  const { math } = pluginProps || {};
31
31
  const { keypadMode, customKeys, controlledKeypadMode = true } = math || {};
@@ -106,12 +106,9 @@ export default function MediaPlugin(type, opts) {
106
106
  type,
107
107
  opts,
108
108
  callback: (val, data) => {
109
- const [node, nodePath] = getNodeBy(
110
- editor,
111
- descendant => descendant.type === type && get(descendant, 'data.newMedia')
112
- );
109
+ const nodePath = ReactEditor.findPath(editor, inline);
113
110
 
114
- if (node) {
111
+ if (inline) {
115
112
  if (!val) {
116
113
  editor.apply({
117
114
  type: 'remove_node',
@@ -122,7 +119,7 @@ export default function MediaPlugin(type, opts) {
122
119
  type: 'set_node',
123
120
  path: nodePath,
124
121
  properties: {
125
- data: node.data
122
+ data: inline.data
126
123
  },
127
124
  newProperties: {
128
125
  data: {
@@ -134,7 +131,7 @@ export default function MediaPlugin(type, opts) {
134
131
  }
135
132
  }
136
133
 
137
- moveFocusAfterMedia(editor, node);
134
+ moveFocusAfterMedia(editor, inline);
138
135
  }
139
136
  });
140
137
  }
@@ -187,12 +184,9 @@ export default function MediaPlugin(type, opts) {
187
184
  type,
188
185
  opts,
189
186
  callback: (val, data) => {
190
- const [nodeIsThere, nodePath] = getNodeBy(
191
- editor,
192
- descendant => descendant.type === type && get(descendant, 'data.editing')
193
- );
187
+ const nodePath = ReactEditor.findPath(editor, nodeToEdit);
194
188
 
195
- if (nodeIsThere && val) {
189
+ if (nodePath && val) {
196
190
  editor.apply({
197
191
  type: 'set_node',
198
192
  path: nodePath,
@@ -9,7 +9,7 @@ export const onValueChange = (nodeProps, n, value) => {
9
9
  change.setNodeByKey(n.key, {
10
10
  data: {
11
11
  ...value,
12
- index: n.data.get('index')
12
+ index: n.data.index
13
13
  }
14
14
  });
15
15
 
@@ -22,12 +22,12 @@ export const onRemoveResponse = (nodeProps, value) => {
22
22
  const val = nodeProps.editor.value;
23
23
  const change = val.change();
24
24
  const dragInTheBlank = val.document.findDescendant(
25
- n => n.data && n.data.get('index') === value.index
25
+ n => n.data && n.data.index === value.index
26
26
  );
27
27
 
28
28
  change.setNodeByKey(dragInTheBlank.key, {
29
29
  data: {
30
- index: dragInTheBlank.data.get('index')
30
+ index: dragInTheBlank.data.index
31
31
  }
32
32
  });
33
33
 
@@ -3,6 +3,8 @@ import { Node as SlateNode } from 'slate';
3
3
  import { jsx } from 'slate-hyperscript';
4
4
  import debug from 'debug';
5
5
 
6
+ import cloneDeep from 'lodash/cloneDeep';
7
+ import isEqual from 'lodash/isEqual';
6
8
  import isUndefined from 'lodash/isUndefined';
7
9
  import InlineDropdown from './inline-dropdown';
8
10
  import DragInTheBlank from './drag-in-the-blank';
@@ -31,15 +33,15 @@ export default function ResponseAreaPlugin(opts) {
31
33
  onClick: editor => {
32
34
  log('[toolbar] onClick');
33
35
  const currentRespAreaList = [];
34
- const descendants = SlateNode.descendants(editor, {
35
- reverse: true
36
- });
36
+ const descendants = Array.from(SlateNode.descendants(editor, { reverse: true })).map(
37
+ ([d]) => d
38
+ );
37
39
 
38
- for (const [descendant, descendantPath] of descendants) {
39
- if (isOfCurrentType(descendant, descendantPath)) {
40
- currentRespAreaList.push(descendant);
40
+ descendants.forEach(d => {
41
+ if (isOfCurrentType(d)) {
42
+ currentRespAreaList.push(d);
41
43
  }
42
- }
44
+ });
43
45
 
44
46
  if (currentRespAreaList.length >= opts.maxResponseAreas) {
45
47
  return;
@@ -91,7 +93,7 @@ export default function ResponseAreaPlugin(opts) {
91
93
  name: 'response_area',
92
94
  toolbar,
93
95
  rules: editor => {
94
- const { isVoid, isInline } = editor;
96
+ const { isVoid, isInline, onChange } = editor;
95
97
 
96
98
  editor.isVoid = element => {
97
99
  return elTypesArray.includes(element.type) ? true : isVoid(element);
@@ -101,6 +103,62 @@ export default function ResponseAreaPlugin(opts) {
101
103
  return elTypesArray.includes(element.type) ? true : isInline(element);
102
104
  };
103
105
 
106
+ let oldEditor = cloneDeep(editor);
107
+
108
+ editor.onChange = options => {
109
+ const descendants = Array.from(SlateNode.descendants(editor, { reverse: true })).map(
110
+ ([d]) => d
111
+ );
112
+ const type = opts.type.replace(/-/g, '_');
113
+
114
+ if (isUndefined(lastIndexMap[type])) {
115
+ lastIndexMap[type] = 0;
116
+
117
+ descendants.forEach(d => {
118
+ if (d.type === type) {
119
+ const newIndex = parseInt(d.data.index, 10);
120
+
121
+ if (newIndex > lastIndexMap[type]) {
122
+ lastIndexMap[type] = newIndex;
123
+ }
124
+ }
125
+ });
126
+ }
127
+
128
+ if (isEqual(editor, oldEditor)) {
129
+ return;
130
+ }
131
+
132
+ const oldDescendants = Array.from(SlateNode.descendants(oldEditor, { reverse: true })).map(
133
+ ([d]) => d
134
+ );
135
+ const currentRespAreaList = descendants.filter(isOfCurrentType);
136
+ const oldRespAreaList = oldDescendants.filter(isOfCurrentType);
137
+
138
+ toolbar.disabled = currentRespAreaList.length >= opts.maxResponseAreas;
139
+
140
+ const arrayToFilter =
141
+ oldRespAreaList.length > currentRespAreaList.length
142
+ ? oldRespAreaList
143
+ : currentRespAreaList;
144
+ const arrayToUseForFilter =
145
+ arrayToFilter === oldRespAreaList ? currentRespAreaList : oldRespAreaList;
146
+
147
+ const elementsWithChangedStatus = arrayToFilter.filter(
148
+ d => !arrayToUseForFilter.find(e => e.data.index === d.data.index)
149
+ );
150
+
151
+ if (
152
+ elementsWithChangedStatus.length &&
153
+ oldRespAreaList.length > currentRespAreaList.length
154
+ ) {
155
+ opts.onHandleAreaChange(elementsWithChangedStatus);
156
+ }
157
+
158
+ oldEditor = cloneDeep(editor);
159
+ onChange(options);
160
+ };
161
+
104
162
  return editor;
105
163
  },
106
164
  filterPlugins: (node, plugins) => {
@@ -110,12 +168,15 @@ export default function ResponseAreaPlugin(opts) {
110
168
 
111
169
  return plugins.filter(p => p.name !== 'response_area');
112
170
  },
113
- deleteNode: (e, node, value, onChange) => {
171
+ deleteNode: (e, node, nodePath, editor, onChange) => {
114
172
  e.preventDefault();
115
173
 
116
- const change = value.change().removeNodeByKey(node.key);
174
+ editor.apply({
175
+ type: 'remove_node',
176
+ path: nodePath
177
+ });
117
178
 
118
- onChange(change);
179
+ onChange(editor);
119
180
  },
120
181
  supports: node => elTypesArray.indexOf(node.type) >= 0,
121
182
  renderNode(props) {
@@ -166,49 +227,6 @@ export default function ResponseAreaPlugin(opts) {
166
227
  );
167
228
  }
168
229
  },
169
- onChange(change, editor) {
170
- const type = opts.type.replace(/-/g, '_');
171
-
172
- if (isUndefined(lastIndexMap[type])) {
173
- lastIndexMap[type] = 0;
174
-
175
- change.value.document.forEachDescendant(d => {
176
- if (d.type === type) {
177
- const newIndex = parseInt(d.data.get('index'), 10);
178
-
179
- if (newIndex > lastIndexMap[type]) {
180
- lastIndexMap[type] = newIndex;
181
- }
182
- }
183
- });
184
- }
185
-
186
- if (!editor.value) {
187
- return;
188
- }
189
-
190
- const currentRespAreaList = change.value.document.filterDescendants(isOfCurrentType);
191
- const oldRespAreaList = editor.value.document.filterDescendants(isOfCurrentType);
192
-
193
- if (currentRespAreaList.size >= opts.maxResponseAreas) {
194
- toolbar.disabled = true;
195
- } else {
196
- toolbar.disabled = false;
197
- }
198
-
199
- const arrayToFilter =
200
- oldRespAreaList.size > currentRespAreaList.size ? oldRespAreaList : currentRespAreaList;
201
- const arrayToUseForFilter =
202
- arrayToFilter === oldRespAreaList ? currentRespAreaList : oldRespAreaList;
203
-
204
- const elementsWithChangedStatus = arrayToFilter.filter(
205
- d => !arrayToUseForFilter.find(e => e.data.get('index') === d.data.get('index'))
206
- );
207
-
208
- if (elementsWithChangedStatus.size && oldRespAreaList.size > currentRespAreaList.size) {
209
- opts.onHandleAreaChange(elementsWithChangedStatus);
210
- }
211
- },
212
230
  onDrop(event, change, editor) {
213
231
  const closestEl = event.target.closest('[data-key]');
214
232
  const inline = editor.value.document.findDescendant(d => d.key === closestEl.dataset.key);
@@ -254,18 +272,14 @@ export const serialization = {
254
272
  }
255
273
  },
256
274
  serialize(object) {
257
- if (object.object !== 'inline') {
258
- return;
259
- }
260
-
261
275
  switch (object.type) {
262
276
  case 'inline_dropdown': {
263
- const data = object.data.toJSON();
277
+ const data = object.data;
264
278
 
265
279
  return <span data-type="inline_dropdown" data-index={data.index} data-value={data.value} />;
266
280
  }
267
281
  case 'explicit_constructed_response': {
268
- const data = object.data.toJSON();
282
+ const data = object.data;
269
283
 
270
284
  return (
271
285
  <span
@@ -276,7 +290,7 @@ export const serialization = {
276
290
  );
277
291
  }
278
292
  case 'drag_in_the_blank': {
279
- const data = object.data.toJSON();
293
+ const data = object.data;
280
294
 
281
295
  return (
282
296
  <span
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { Node as SlateNode, Editor, Transforms } from 'slate';
2
+ import { Node as SlateNode, Element as SlateElement, Editor, Transforms } from 'slate';
3
3
  import debug from 'debug';
4
4
  import GridOn from '@material-ui/icons/GridOn';
5
5
  import TableToolbar from './table-toolbar';
@@ -128,7 +128,70 @@ const TABLE_TYPES = ['tbody', 'tr', 'td', 'table'];
128
128
 
129
129
  export default (opts, toolbarPlugins /* : {toolbar: {}}[] */) => {
130
130
  const core = {
131
- utils: {}
131
+ utils: {},
132
+ rules: editor => {
133
+ const { normalizeNode } = editor;
134
+
135
+ editor.normalizeNode = entry => {
136
+ const [tableNode, tablePath] = entry;
137
+ const tableParent = SlateNode.get(editor, tablePath.slice(0, -1));
138
+
139
+ // If the element is a paragraph, ensure its children are valid.
140
+ if (SlateElement.isElement(tableNode) && tableNode.type === 'table') {
141
+ const emptyBlock = {
142
+ type: 'paragraph',
143
+ children: [{ text: '' }]
144
+ };
145
+ const tableIndex = tablePath.slice(-1)[0];
146
+
147
+ // if table is the first element, we need to add a space before
148
+ // so users can focus before the table
149
+ if (tableIndex === 0) {
150
+ const beforeTablePath = [...tablePath.slice(0, -1), 0];
151
+
152
+ editor.apply({
153
+ type: 'insert_node',
154
+ path: beforeTablePath,
155
+ node: emptyBlock
156
+ });
157
+ editor.continueNormalization();
158
+ return;
159
+ }
160
+
161
+ // if table is the last element, we add element after it
162
+ if (tableParent.children.length - 1 === tableIndex) {
163
+ const afterTablePath = [...tablePath.slice(0, -1), tableIndex + 1];
164
+
165
+ editor.apply({
166
+ type: 'insert_node',
167
+ path: afterTablePath,
168
+ node: emptyBlock
169
+ });
170
+ editor.continueNormalization();
171
+ return;
172
+ }
173
+
174
+ // if table does not have a tbody, we add it
175
+ if (tableNode.children[0].type !== 'tbody') {
176
+ const tBodyNode = { type: 'tbody', children: [] };
177
+
178
+ Transforms.wrapNodes(editor, tBodyNode, {
179
+ at: {
180
+ anchor: { path: [...tablePath, 0], offset: 0 },
181
+ focus: { path: [...tablePath, tableNode.children.length], offset: 0 }
182
+ }
183
+ });
184
+ editor.continueNormalization();
185
+ return;
186
+ }
187
+ }
188
+
189
+ // Fall back to the original `normalizeNode` to enforce other constraints.
190
+ normalizeNode(entry);
191
+ };
192
+
193
+ return editor;
194
+ }
132
195
  };
133
196
 
134
197
  core.utils.createTable = (row = 2, columns = 2) => {
@@ -192,7 +255,7 @@ export default (opts, toolbarPlugins /* : {toolbar: {}}[] */) => {
192
255
  /**
193
256
  * Note - the node may not be a table node - it may be a node inside a table.
194
257
  */
195
- customToolbar: (node, value, editor, onToolbarDone) => {
258
+ customToolbar: (node, nodePath, editor, onToolbarDone) => {
196
259
  log('[customToolbar] node.data: ', node.data);
197
260
 
198
261
  const [tableBlock] = core.utils.getTableBlock(editor);
@@ -317,7 +380,7 @@ export default (opts, toolbarPlugins /* : {toolbar: {}}[] */) => {
317
380
 
318
381
  const onDone = () => {
319
382
  log('[onDone] call onToolbarDone...');
320
- onToolbarDone(null, true);
383
+ onToolbarDone(true);
321
384
  };
322
385
 
323
386
  const Tb = () => (
@@ -339,7 +402,7 @@ export default (opts, toolbarPlugins /* : {toolbar: {}}[] */) => {
339
402
  }
340
403
  };
341
404
 
342
- core.supports = (node, editor) => TABLE_TYPES.includes(node.type);
405
+ core.supports = node => TABLE_TYPES.includes(node.type);
343
406
 
344
407
  const Node = props => {
345
408
  switch (props.node.type) {
@@ -16,7 +16,7 @@ import { removeDialogs as removeCharacterDialogs } from '../characters';
16
16
 
17
17
  const log = debug('@pie-lib:editable-html:plugins:toolbar');
18
18
 
19
- const getCustomToolbar = (plugin, node, value, editor, handleDone) => {
19
+ const getCustomToolbar = (plugin, node, nodePath, editor, handleDone) => {
20
20
  if (!plugin) {
21
21
  return;
22
22
  }
@@ -33,7 +33,7 @@ const getCustomToolbar = (plugin, node, value, editor, handleDone) => {
33
33
  return plugin.toolbar.CustomToolbarComp;
34
34
  } else if (typeof plugin.toolbar.customToolbar === 'function') {
35
35
  log('deprecated - use CustomToolbarComp');
36
- return plugin.toolbar.customToolbar(node, value, editor, handleDone);
36
+ return plugin.toolbar.customToolbar(node, nodePath, editor, handleDone);
37
37
  }
38
38
  };
39
39
 
@@ -120,12 +120,12 @@ export const Toolbar = props => {
120
120
  };
121
121
  };
122
122
 
123
- const onToolbarDone = (change, finishEditing) => {
124
- log('[onToolbarDone] change: ', change, 'finishEditing: ', finishEditing);
123
+ const onToolbarDone = (editor, finishEditing) => {
124
+ log('[onToolbarDone] change: ', editor, 'finishEditing: ', finishEditing);
125
125
  const { onChange, onDone } = props;
126
126
 
127
- if (change) {
128
- onChange(change, () => {
127
+ if (editor) {
128
+ onChange(editor, () => {
129
129
  if (finishEditing) {
130
130
  onDone();
131
131
  }
@@ -139,13 +139,13 @@ export const Toolbar = props => {
139
139
  };
140
140
 
141
141
  const onDeleteClick = debounce(
142
- (e, plugin, node, value, onChange) => plugin.deleteNode(e, node, value, onChange),
142
+ (e, plugin, node, nodePath, editor, onChange) => plugin.deleteNode(e, node, nodePath, editor, onChange),
143
143
  500
144
144
  );
145
145
 
146
- const onDeleteMouseDown = (e, plugin, node, value, onChange) => {
146
+ const onDeleteMouseDown = e => {
147
147
  e.persist();
148
- onDeleteClick(e, plugin, node, value, onChange);
148
+ onDeleteClick(e, plugin, node, nodePath, editor, onChange);
149
149
  };
150
150
 
151
151
  const {
@@ -210,21 +210,28 @@ export const Toolbar = props => {
210
210
 
211
211
  log('[render] plugin: ', plugin);
212
212
 
213
- const handleDone = editor => {
213
+ const handleDone = done => {
214
+ const isDone = done ? editor : null;
214
215
  let handler = onDone;
215
216
 
216
217
  if (plugin && plugin.toolbar && plugin.toolbar.customToolbar) {
217
218
  handler = onToolbarDone;
218
219
  }
219
220
 
220
- handler(editor);
221
+ if (isDone) {
222
+ editor.selection = null;
223
+ }
224
+
225
+ handler(isDone);
221
226
 
222
227
  if (parentPlugin && parentPlugin.handleDone) {
223
- parentPlugin.handleDone(editor, node, plugin);
228
+ parentPlugin.handleDone(isDone, node, plugin);
224
229
  }
225
230
  };
226
231
 
227
- const CustomToolbar = getCustomToolbar(plugin, node, value, editor, handleDone);
232
+ const CustomToolbar =
233
+ getCustomToolbar(plugin, node, nodePath, editor, handleDone) ||
234
+ getCustomToolbar(parentPlugin, node, nodePath, editor, handleDone);
228
235
 
229
236
  const filteredPlugins =
230
237
  plugin && plugin.filterPlugins ? plugin.filterPlugins(node, plugins) : plugins;
@@ -290,7 +297,7 @@ export const Toolbar = props => {
290
297
  <IconButton
291
298
  aria-label="Delete"
292
299
  className={classes.iconRoot}
293
- onMouseDown={e => onDeleteMouseDown(e, plugin, node, value, onChange)}
300
+ onMouseDown={e => onDeleteMouseDown(e)}
294
301
  classes={{
295
302
  root: classes.iconRoot
296
303
  }}