@atlaskit/editor-plugin-block-controls 1.4.20 → 1.4.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @atlaskit/editor-plugin-block-controls
2
2
 
3
+ ## 1.4.22
4
+
5
+ ### Patch Changes
6
+
7
+ - [#111855](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/111855)
8
+ [`858997cc1d1e0`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/858997cc1d1e0) -
9
+ ED-23619 Improve drop latency by cleaning up decorations before they are being redrawn
10
+
11
+ ## 1.4.21
12
+
13
+ ### Patch Changes
14
+
15
+ - [#111695](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/111695)
16
+ [`4dadac69832d7`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/4dadac69832d7) -
17
+ Refactor Pragmatic usage
18
+ - [#111695](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/111695)
19
+ [`c8e7bf89f5bd8`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/c8e7bf89f5bd8) -
20
+ reduce rerenders in mousemovewrapper
21
+
3
22
  ## 1.4.20
4
23
 
5
24
  ### Patch Changes
@@ -14,6 +14,7 @@ var _dropTarget = require("../ui/drop-target");
14
14
  var _mouseMoveWrapper = require("../ui/mouse-move-wrapper");
15
15
  var dropTargetDecorations = exports.dropTargetDecorations = function dropTargetDecorations(oldState, newState, api) {
16
16
  var decs = [];
17
+ unmountDecorations('data-blocks-drop-target-container');
17
18
  // Decoration state is used to keep track of the position of the drop targets
18
19
  // and allows us to easily map the updated position in the plugin apply method.
19
20
  var decorationState = [];
@@ -24,6 +25,7 @@ var dropTargetDecorations = exports.dropTargetDecorations = function dropTargetD
24
25
  });
25
26
  decs.push(_view.Decoration.widget(pos, function () {
26
27
  var element = document.createElement('div');
28
+ element.setAttribute('data-blocks-drop-target-container', 'true');
27
29
  _reactDom.default.render( /*#__PURE__*/(0, _react.createElement)(_dropTarget.DropTarget, {
28
30
  api: api,
29
31
  index: index
@@ -48,6 +50,7 @@ var dropTargetDecorations = exports.dropTargetDecorations = function dropTargetD
48
50
  });
49
51
  decs.push(_view.Decoration.widget(newState.doc.nodeSize - 2, function () {
50
52
  var element = document.createElement('div');
53
+ element.setAttribute('data-blocks-drop-target-container', 'true');
51
54
  _reactDom.default.render( /*#__PURE__*/(0, _react.createElement)(_dropTarget.DropTarget, {
52
55
  api: api,
53
56
  index: decorationState.length
@@ -80,10 +83,12 @@ var nodeDecorations = exports.nodeDecorations = function nodeDecorations(newStat
80
83
  */
81
84
  var mouseMoveWrapperDecorations = exports.mouseMoveWrapperDecorations = function mouseMoveWrapperDecorations(newState, api) {
82
85
  var decs = [];
86
+ unmountDecorations('data-blocks-decoration-container');
83
87
  newState.doc.descendants(function (node, pos, _parent, index) {
84
88
  var anchorName = "--node-anchor-".concat(node.type.name, "-").concat(index);
85
89
  decs.push(_view.Decoration.widget(pos, function (view, getPos) {
86
90
  var element = document.createElement('div');
91
+ element.setAttribute('data-blocks-decoration-container', 'true');
87
92
  _reactDom.default.render( /*#__PURE__*/(0, _react.createElement)(_mouseMoveWrapper.MouseMoveWrapper, {
88
93
  view: view,
89
94
  api: api,
@@ -108,6 +113,7 @@ var dragHandleDecoration = exports.dragHandleDecoration = function dragHandleDec
108
113
  return _view.Decoration.widget(pos, function (view, getPos) {
109
114
  var element = document.createElement('div');
110
115
  element.setAttribute('data-testid', 'block-ctrl-decorator-widget');
116
+ element.setAttribute('data-blocks-drag-handle-container', 'true');
111
117
  _reactDom.default.render( /*#__PURE__*/(0, _react.createElement)(_dragHandle.DragHandle, {
112
118
  view: view,
113
119
  api: api,
@@ -118,6 +124,17 @@ var dragHandleDecoration = exports.dragHandleDecoration = function dragHandleDec
118
124
  return element;
119
125
  }, {
120
126
  side: -1,
121
- id: 'drag-handle'
127
+ id: 'drag-handle',
128
+ destroy: function destroy(node) {
129
+ _reactDom.default.unmountComponentAtNode(node);
130
+ }
131
+ });
132
+ };
133
+ var unmountDecorations = function unmountDecorations(selector) {
134
+ // Removing decorations manually instead of using native destroy function in prosemirror API
135
+ // as it was more responsive and causes less re-rendering
136
+ var decorationsToRemove = document.querySelectorAll("[".concat(selector, "=\"true\"]"));
137
+ decorationsToRemove.forEach(function (el) {
138
+ _reactDom.default.unmountComponentAtNode(el);
122
139
  });
123
140
  };
@@ -12,26 +12,46 @@ var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
12
12
  var _state = require("@atlaskit/editor-prosemirror/state");
13
13
  var _view = require("@atlaskit/editor-prosemirror/view");
14
14
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
15
+ var _element = require("@atlaskit/pragmatic-drag-and-drop-auto-scroll/element");
16
+ var _combine = require("@atlaskit/pragmatic-drag-and-drop/combine");
15
17
  var _adapter = require("@atlaskit/pragmatic-drag-and-drop/element/adapter");
16
18
  var _decorations = require("./decorations");
17
19
  var key = exports.key = new _state.PluginKey('blockControls');
18
20
  var destroyFn = function destroyFn(api) {
19
- return (0, _adapter.monitorForElements)({
21
+ var scrollable = document.querySelector('.fabric-editor-popup-scroll-parent');
22
+ var cleanupFn = [];
23
+ if (scrollable) {
24
+ cleanupFn.push((0, _element.autoScrollForElements)({
25
+ element: scrollable
26
+ }));
27
+ }
28
+ cleanupFn.push((0, _adapter.monitorForElements)({
20
29
  canMonitor: function canMonitor(_ref) {
21
30
  var source = _ref.source;
22
31
  return source.data.type === 'element';
23
32
  },
33
+ onDragStart: function onDragStart() {
34
+ scrollable.style.setProperty('scroll-behavior', 'unset');
35
+ },
24
36
  onDrop: function onDrop(_ref2) {
37
+ var _api$core;
25
38
  var location = _ref2.location,
26
39
  source = _ref2.source;
40
+ scrollable.style.setProperty('scroll-behavior', null);
41
+ api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref3) {
42
+ var tr = _ref3.tr;
43
+ return tr.setMeta(key, {
44
+ isDragging: false
45
+ });
46
+ });
27
47
  // if no drop targets are rendered, assume that drop is invalid
28
48
  if (location.current.dropTargets.length === 0) {
29
- var _api$core;
30
- var _ref3 = source.data,
31
- start = _ref3.start;
32
- api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref4) {
49
+ var _api$core2;
50
+ var _ref4 = source.data,
51
+ start = _ref4.start;
52
+ api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.execute(function (_ref5) {
33
53
  var _api$analytics;
34
- var tr = _ref4.tr;
54
+ var tr = _ref5.tr;
35
55
  var resolvedMovingNode = tr.doc.resolve(start);
36
56
  var maybeNode = resolvedMovingNode.nodeAfter;
37
57
  api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.attachAnalyticsEvent({
@@ -48,7 +68,8 @@ var destroyFn = function destroyFn(api) {
48
68
  });
49
69
  }
50
70
  }
51
- });
71
+ }));
72
+ return _combine.combine.apply(void 0, cleanupFn);
52
73
  };
53
74
  var initialState = {
54
75
  decorations: _view.DecorationSet.empty,
@@ -56,8 +77,6 @@ var initialState = {
56
77
  activeNode: null,
57
78
  isDragging: false,
58
79
  isMenuOpen: false,
59
- start: null,
60
- end: null,
61
80
  editorHeight: 0,
62
81
  isResizerResizing: false,
63
82
  isDocSizeLimitEnabled: false
@@ -74,7 +93,7 @@ var createPlugin = exports.createPlugin = function createPlugin(api) {
74
93
  return initialState;
75
94
  },
76
95
  apply: function apply(tr, currentState, oldState, newState) {
77
- var _decorationState, _meta$activeNode, _meta$isDragging, _meta$editorHeight, _isResizerResizing;
96
+ var _decorationState, _meta$activeNode, _meta$isDragging, _meta$editorHeight;
78
97
  if (initialState.isDocSizeLimitEnabled && newState.doc.nodeSize > DRAG_AND_DROP_DOC_SIZE_LIMIT) {
79
98
  return initialState;
80
99
  }
@@ -89,29 +108,31 @@ var createPlugin = exports.createPlugin = function createPlugin(api) {
89
108
  // If tables or media are being resized, we want to hide the drag handle
90
109
  var resizerMeta = tr.getMeta('is-resizer-resizing');
91
110
  isResizerResizing = resizerMeta !== null && resizerMeta !== void 0 ? resizerMeta : isResizerResizing;
92
- var isEmptyDoc = newState.doc.childCount === 1 && newState.doc.nodeSize <= 4;
93
-
94
- // This is not targeted enough - it's trying to catch events like expand being set to breakout
95
- var maybeWidthUpdated = tr.docChanged && oldState.doc.nodeSize === newState.doc.nodeSize && oldState.doc.childCount === newState.doc.childCount;
96
111
  var nodeCountChanged = oldState.doc.childCount !== newState.doc.childCount;
97
112
 
98
113
  // During resize, remove the drag handle widget
99
- if (isResizerResizing || nodeCountChanged) {
100
- var oldHandle = decorations.find().filter(function (_ref5) {
101
- var spec = _ref5.spec;
114
+ if (isResizerResizing || nodeCountChanged || meta !== null && meta !== void 0 && meta.nodeMoved) {
115
+ var oldHandle = decorations.find().filter(function (_ref6) {
116
+ var spec = _ref6.spec;
102
117
  return spec.id === 'drag-handle';
103
118
  });
104
119
  decorations = decorations.remove(oldHandle);
105
120
  }
106
121
 
122
+ // This is not targeted enough - it's trying to catch events like expand being set to breakout
123
+ var maybeWidthUpdated = tr.docChanged && oldState.doc.nodeSize === newState.doc.nodeSize && !nodeCountChanged;
124
+ var redrawDecorations = decorations === _view.DecorationSet.empty || (meta === null || meta === void 0 ? void 0 : meta.editorHeight) !== undefined && (meta === null || meta === void 0 ? void 0 : meta.editorHeight) !== editorHeight || maybeWidthUpdated || nodeCountChanged || resizerMeta === false || !!(meta !== null && meta !== void 0 && meta.nodeMoved) && tr.docChanged;
125
+
107
126
  // Draw node and mouseWrapper decorations at top level node if decorations is empty, editor height changes or node is moved
108
- var redrawDecorations = decorations === _view.DecorationSet.empty || (meta === null || meta === void 0 ? void 0 : meta.editorHeight) !== undefined && (meta === null || meta === void 0 ? void 0 : meta.editorHeight) !== editorHeight || maybeWidthUpdated || nodeCountChanged || resizerMeta === false || (meta === null || meta === void 0 ? void 0 : meta.nodeMoved) && tr.docChanged;
109
- if (redrawDecorations && isResizerResizing === false && api) {
127
+ if (redrawDecorations && !isResizerResizing && api) {
110
128
  decorations = _view.DecorationSet.create(newState.doc, []);
111
129
  var nodeDecs = (0, _decorations.nodeDecorations)(newState);
112
130
  var mouseWrapperDecs = (0, _decorations.mouseMoveWrapperDecorations)(newState, api);
113
131
  decorations = decorations.add(newState.doc, [].concat((0, _toConsumableArray2.default)(nodeDecs), (0, _toConsumableArray2.default)(mouseWrapperDecs)));
114
- if (activeNode) {
132
+
133
+ // Note: Quite often the handle is not in the right position after a node is moved
134
+ // it is safer for now to not show it when a node is moved
135
+ if (activeNode && !(meta !== null && meta !== void 0 && meta.nodeMoved)) {
115
136
  var draghandleDec = (0, _decorations.dragHandleDecoration)(activeNode.pos, activeNode.anchorName, activeNode.nodeType, api);
116
137
  decorations = decorations.add(newState.doc, [draghandleDec]);
117
138
  }
@@ -119,8 +140,8 @@ var createPlugin = exports.createPlugin = function createPlugin(api) {
119
140
 
120
141
  // Remove previous drag handle widget and draw new drag handle widget when activeNode changes
121
142
  if (meta !== null && meta !== void 0 && meta.activeNode && (meta === null || meta === void 0 ? void 0 : meta.activeNode.pos) !== (activeNode === null || activeNode === void 0 ? void 0 : activeNode.pos) && (meta === null || meta === void 0 ? void 0 : meta.activeNode.anchorName) !== (activeNode === null || activeNode === void 0 ? void 0 : activeNode.anchorName) && api) {
122
- var _oldHandle = decorations.find().filter(function (_ref6) {
123
- var spec = _ref6.spec;
143
+ var _oldHandle = decorations.find().filter(function (_ref7) {
144
+ var spec = _ref7.spec;
124
145
  return spec.id === 'drag-handle';
125
146
  });
126
147
  decorations = decorations.remove(_oldHandle);
@@ -139,8 +160,8 @@ var createPlugin = exports.createPlugin = function createPlugin(api) {
139
160
 
140
161
  // Remove drop target decoration when dragging stops
141
162
  if ((meta === null || meta === void 0 ? void 0 : meta.isDragging) === false && !tr.docChanged) {
142
- var dropTargetDecs = decorations.find().filter(function (_ref7) {
143
- var spec = _ref7.spec;
163
+ var dropTargetDecs = decorations.find().filter(function (_ref8) {
164
+ var spec = _ref8.spec;
144
165
  return spec.type === 'drop-target-decoration';
145
166
  });
146
167
  decorations = decorations.remove(dropTargetDecs);
@@ -148,9 +169,9 @@ var createPlugin = exports.createPlugin = function createPlugin(api) {
148
169
 
149
170
  // Map drop target decoration positions when the document changes
150
171
  if (tr.docChanged && currentState.isDragging) {
151
- decorationState = decorationState.map(function (_ref8) {
152
- var index = _ref8.index,
153
- pos = _ref8.pos;
172
+ decorationState = decorationState.map(function (_ref9) {
173
+ var index = _ref9.index,
174
+ pos = _ref9.pos;
154
175
  return {
155
176
  index: index,
156
177
  pos: tr.mapping.map(pos)
@@ -169,6 +190,7 @@ var createPlugin = exports.createPlugin = function createPlugin(api) {
169
190
  anchorName: activeNode.anchorName,
170
191
  nodeType: activeNode.nodeType
171
192
  } : activeNode;
193
+ var isEmptyDoc = newState.doc.childCount === 1 && newState.doc.nodeSize <= 4;
172
194
  return {
173
195
  decorations: decorations,
174
196
  decorationState: (_decorationState = decorationState) !== null && _decorationState !== void 0 ? _decorationState : currentState.decorationState,
@@ -176,7 +198,7 @@ var createPlugin = exports.createPlugin = function createPlugin(api) {
176
198
  isDragging: (_meta$isDragging = meta === null || meta === void 0 ? void 0 : meta.isDragging) !== null && _meta$isDragging !== void 0 ? _meta$isDragging : currentState.isDragging,
177
199
  isMenuOpen: meta !== null && meta !== void 0 && meta.toggleMenu ? !isMenuOpen : isMenuOpen,
178
200
  editorHeight: (_meta$editorHeight = meta === null || meta === void 0 ? void 0 : meta.editorHeight) !== null && _meta$editorHeight !== void 0 ? _meta$editorHeight : currentState.editorHeight,
179
- isResizerResizing: (_isResizerResizing = isResizerResizing) !== null && _isResizerResizing !== void 0 ? _isResizerResizing : isResizerResizing,
201
+ isResizerResizing: isResizerResizing,
180
202
  isDocSizeLimitEnabled: initialState.isDocSizeLimitEnabled
181
203
  };
182
204
  }
@@ -209,10 +231,13 @@ var createPlugin = exports.createPlugin = function createPlugin(api) {
209
231
 
210
232
  // Start observing the editor DOM element
211
233
  resizeObserver.observe(dom);
234
+
235
+ // Start pragmatic monitors
236
+ var pragmaticCleanup = destroyFn(api);
212
237
  return {
213
238
  destroy: function destroy() {
214
239
  resizeObserver.unobserve(dom);
215
- return destroyFn(api);
240
+ pragmaticCleanup();
216
241
  }
217
242
  };
218
243
  }
@@ -132,15 +132,6 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
132
132
  }
133
133
  api === null || api === void 0 || (_api$core5 = api.core) === null || _api$core5 === void 0 || _api$core5.actions.execute(api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.commands.setNodeDragged(start, anchorName, nodeType));
134
134
  api === null || api === void 0 || (_api$core6 = api.core) === null || _api$core6 === void 0 || _api$core6.actions.focus();
135
- },
136
- onDrop: function onDrop() {
137
- var _api$core7;
138
- api === null || api === void 0 || (_api$core7 = api.core) === null || _api$core7 === void 0 || _api$core7.actions.execute(function (_ref6) {
139
- var tr = _ref6.tr;
140
- return tr.setMeta(_main.key, {
141
- isDragging: false
142
- });
143
- });
144
135
  }
145
136
  });
146
137
  }, [api, start, view, anchorName, nodeType]);
@@ -10,9 +10,7 @@ var _react = require("react");
10
10
  var _react2 = require("@emotion/react");
11
11
  var _analytics = require("@atlaskit/editor-common/analytics");
12
12
  var _hooks = require("@atlaskit/editor-common/hooks");
13
- var _element = require("@atlaskit/pragmatic-drag-and-drop-auto-scroll/element");
14
13
  var _box = require("@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/box");
15
- var _combine = require("@atlaskit/pragmatic-drag-and-drop/combine");
16
14
  var _adapter = require("@atlaskit/pragmatic-drag-and-drop/element/adapter");
17
15
  /** @jsx jsx */
18
16
 
@@ -47,21 +45,11 @@ var DropTarget = exports.DropTarget = function DropTarget(_ref) {
47
45
  if (!element) {
48
46
  return;
49
47
  }
50
- var combined = [];
51
- var scrollable = document.querySelector('.fabric-editor-popup-scroll-parent');
52
- if (scrollable) {
53
- combined.push((0, _element.autoScrollForElements)({
54
- element: scrollable
55
- }));
56
- }
57
- combined.push((0, _adapter.dropTargetForElements)({
48
+ return (0, _adapter.dropTargetForElements)({
58
49
  element: element,
59
50
  getIsSticky: function getIsSticky() {
60
51
  return true;
61
52
  },
62
- onDrag: function onDrag() {
63
- scrollable.style.setProperty('scroll-behavior', 'unset');
64
- },
65
53
  onDragEnter: function onDragEnter() {
66
54
  setIsDraggedOver(true);
67
55
  },
@@ -70,7 +58,6 @@ var DropTarget = exports.DropTarget = function DropTarget(_ref) {
70
58
  },
71
59
  onDrop: function onDrop() {
72
60
  var _api$blockControls;
73
- scrollable.style.setProperty('scroll-behavior', null);
74
61
  var _ref2 = (api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.sharedState.currentState()) || {},
75
62
  activeNode = _ref2.activeNode,
76
63
  decorationState = _ref2.decorationState;
@@ -103,8 +90,7 @@ var DropTarget = exports.DropTarget = function DropTarget(_ref) {
103
90
  });
104
91
  }
105
92
  }
106
- }));
107
- return _combine.combine.apply(void 0, combined);
93
+ });
108
94
  }, [index, api]);
109
95
  return (
110
96
  // Note: Firefox has trouble with using a button element as the handle for drag and drop
@@ -9,7 +9,6 @@ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/sli
9
9
  var _react = require("react");
10
10
  var _react2 = require("@emotion/react");
11
11
  var _bindEventListener = require("bind-event-listener");
12
- var _hooks = require("@atlaskit/editor-common/hooks");
13
12
  var _dragHandlePositions = require("../utils/drag-handle-positions");
14
13
  /** @jsx jsx */
15
14
 
@@ -29,21 +28,17 @@ var MouseMoveWrapper = exports.MouseMoveWrapper = function MouseMoveWrapper(_ref
29
28
  anchorName = _ref.anchorName,
30
29
  nodeType = _ref.nodeType,
31
30
  getPos = _ref.getPos;
32
- var _useSharedPluginState = (0, _hooks.useSharedPluginState)(api, ['blockControls']),
33
- blockControlsState = _useSharedPluginState.blockControlsState;
31
+ // Using a ref for isDragging reduce re-renders
32
+ var isDragging = (0, _react.useRef)(false);
34
33
  var _useState = (0, _react.useState)(false),
35
34
  _useState2 = (0, _slicedToArray2.default)(_useState, 2),
36
- isDragging = _useState2[0],
37
- setIsDragging = _useState2[1];
38
- var _useState3 = (0, _react.useState)(false),
39
- _useState4 = (0, _slicedToArray2.default)(_useState3, 2),
40
- hideWrapper = _useState4[0],
41
- setHideWrapper = _useState4[1];
35
+ hideWrapper = _useState2[0],
36
+ setHideWrapper = _useState2[1];
42
37
  var ref = (0, _react.useRef)(null);
43
- var _useState5 = (0, _react.useState)(),
44
- _useState6 = (0, _slicedToArray2.default)(_useState5, 2),
45
- pos = _useState6[0],
46
- setPos = _useState6[1];
38
+ var _useState3 = (0, _react.useState)(),
39
+ _useState4 = (0, _slicedToArray2.default)(_useState3, 2),
40
+ pos = _useState4[0],
41
+ setPos = _useState4[1];
47
42
  (0, _react.useEffect)(function () {
48
43
  // Adding this event listener to fix issue where wrapper isn't hidden if user navigates to node before page finishes loading
49
44
  // This will be removed when we refactor to remove this component
@@ -63,34 +58,42 @@ var MouseMoveWrapper = exports.MouseMoveWrapper = function MouseMoveWrapper(_ref
63
58
  };
64
59
  }, []);
65
60
  var onMouseEnter = (0, _react.useCallback)(function () {
66
- if (!isDragging) {
61
+ if (!isDragging.current) {
67
62
  setHideWrapper(true);
68
63
  }
69
64
  var pos = getPos();
70
65
  if (pos === undefined) {
71
66
  return;
72
67
  }
73
- if (api && api.blockControls && !isDragging) {
68
+ if (api && api.blockControls && !isDragging.current) {
74
69
  var _api$core;
75
70
  api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(api.blockControls.commands.showDragHandleAt(pos, anchorName, nodeType));
76
71
  }
77
- }, [setHideWrapper, getPos, isDragging, api, anchorName, nodeType]);
78
- (0, _react.useEffect)(function () {
79
- var _blockControlsState$a;
80
- setIsDragging(Boolean(blockControlsState === null || blockControlsState === void 0 ? void 0 : blockControlsState.isDragging));
81
- var pos = getPos();
82
- if (!(blockControlsState !== null && blockControlsState !== void 0 && blockControlsState.activeNode)) {
83
- return;
84
- }
85
- if ((blockControlsState === null || blockControlsState === void 0 || (_blockControlsState$a = blockControlsState.activeNode) === null || _blockControlsState$a === void 0 ? void 0 : _blockControlsState$a.pos) !== pos && !(blockControlsState !== null && blockControlsState !== void 0 && blockControlsState.isDragging)) {
72
+ }, [getPos, isDragging, api, anchorName, nodeType]);
73
+
74
+ //THIS IS TRIGGERED A LOT!
75
+ var onSharedStateChange = (0, _react.useCallback)(function (_ref2) {
76
+ var _nextSharedState$acti;
77
+ var nextSharedState = _ref2.nextSharedState;
78
+ if ((nextSharedState === null || nextSharedState === void 0 || (_nextSharedState$acti = nextSharedState.activeNode) === null || _nextSharedState$acti === void 0 ? void 0 : _nextSharedState$acti.anchorName) !== anchorName && !isDragging.current) {
86
79
  setHideWrapper(false);
87
- return;
88
80
  }
89
- if (blockControlsState !== null && blockControlsState !== void 0 && blockControlsState.isDragging) {
81
+ if (nextSharedState !== null && nextSharedState !== void 0 && nextSharedState.isDragging && !isDragging.current) {
82
+ isDragging.current = true;
90
83
  setHideWrapper(true);
91
- return;
92
84
  }
93
- }, [getPos, blockControlsState]);
85
+ if ((nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.isDragging) === false && isDragging.current) {
86
+ isDragging.current = false;
87
+ setHideWrapper(false);
88
+ }
89
+ }, [anchorName]);
90
+ (0, _react.useEffect)(function () {
91
+ var _api$blockControls;
92
+ var unbind = api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.sharedState.onChange(onSharedStateChange);
93
+ return function () {
94
+ unbind === null || unbind === void 0 || unbind();
95
+ };
96
+ }, [onSharedStateChange, api]);
94
97
  (0, _react.useLayoutEffect)(function () {
95
98
  var supportsAnchor = CSS.supports('height', "anchor-size(".concat(anchorName, " height)")) && CSS.supports('top', "anchor(".concat(anchorName, " start)"));
96
99
  if (supportsAnchor) {
@@ -6,6 +6,7 @@ import { DropTarget } from '../ui/drop-target';
6
6
  import { MouseMoveWrapper } from '../ui/mouse-move-wrapper';
7
7
  export const dropTargetDecorations = (oldState, newState, api) => {
8
8
  const decs = [];
9
+ unmountDecorations('data-blocks-drop-target-container');
9
10
  // Decoration state is used to keep track of the position of the drop targets
10
11
  // and allows us to easily map the updated position in the plugin apply method.
11
12
  const decorationState = [];
@@ -16,6 +17,7 @@ export const dropTargetDecorations = (oldState, newState, api) => {
16
17
  });
17
18
  decs.push(Decoration.widget(pos, () => {
18
19
  const element = document.createElement('div');
20
+ element.setAttribute('data-blocks-drop-target-container', 'true');
19
21
  ReactDOM.render( /*#__PURE__*/createElement(DropTarget, {
20
22
  api,
21
23
  index
@@ -40,6 +42,7 @@ export const dropTargetDecorations = (oldState, newState, api) => {
40
42
  });
41
43
  decs.push(Decoration.widget(newState.doc.nodeSize - 2, () => {
42
44
  const element = document.createElement('div');
45
+ element.setAttribute('data-blocks-drop-target-container', 'true');
43
46
  ReactDOM.render( /*#__PURE__*/createElement(DropTarget, {
44
47
  api,
45
48
  index: decorationState.length
@@ -73,10 +76,12 @@ export const nodeDecorations = newState => {
73
76
  */
74
77
  export const mouseMoveWrapperDecorations = (newState, api) => {
75
78
  const decs = [];
79
+ unmountDecorations('data-blocks-decoration-container');
76
80
  newState.doc.descendants((node, pos, _parent, index) => {
77
81
  const anchorName = `--node-anchor-${node.type.name}-${index}`;
78
82
  decs.push(Decoration.widget(pos, (view, getPos) => {
79
83
  const element = document.createElement('div');
84
+ element.setAttribute('data-blocks-decoration-container', 'true');
80
85
  ReactDOM.render( /*#__PURE__*/createElement(MouseMoveWrapper, {
81
86
  view,
82
87
  api,
@@ -101,6 +106,7 @@ export const dragHandleDecoration = (pos, anchorName, nodeType, api) => {
101
106
  return Decoration.widget(pos, (view, getPos) => {
102
107
  const element = document.createElement('div');
103
108
  element.setAttribute('data-testid', 'block-ctrl-decorator-widget');
109
+ element.setAttribute('data-blocks-drag-handle-container', 'true');
104
110
  ReactDOM.render( /*#__PURE__*/createElement(DragHandle, {
105
111
  view,
106
112
  api,
@@ -111,6 +117,17 @@ export const dragHandleDecoration = (pos, anchorName, nodeType, api) => {
111
117
  return element;
112
118
  }, {
113
119
  side: -1,
114
- id: 'drag-handle'
120
+ id: 'drag-handle',
121
+ destroy: node => {
122
+ ReactDOM.unmountComponentAtNode(node);
123
+ }
124
+ });
125
+ };
126
+ const unmountDecorations = selector => {
127
+ // Removing decorations manually instead of using native destroy function in prosemirror API
128
+ // as it was more responsive and causes less re-rendering
129
+ const decorationsToRemove = document.querySelectorAll(`[${selector}="true"]`);
130
+ decorationsToRemove.forEach(el => {
131
+ ReactDOM.unmountComponentAtNode(el);
115
132
  });
116
133
  };
@@ -4,25 +4,46 @@ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
4
4
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
5
5
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
6
6
  import { getBooleanFF } from '@atlaskit/platform-feature-flags';
7
+ import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
8
+ import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
7
9
  import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
8
10
  import { dragHandleDecoration, dropTargetDecorations, mouseMoveWrapperDecorations, nodeDecorations } from './decorations';
9
11
  export const key = new PluginKey('blockControls');
10
12
  const destroyFn = api => {
11
- return monitorForElements({
13
+ const scrollable = document.querySelector('.fabric-editor-popup-scroll-parent');
14
+ const cleanupFn = [];
15
+ if (scrollable) {
16
+ cleanupFn.push(autoScrollForElements({
17
+ element: scrollable
18
+ }));
19
+ }
20
+ cleanupFn.push(monitorForElements({
12
21
  canMonitor: ({
13
22
  source
14
23
  }) => source.data.type === 'element',
24
+ onDragStart: () => {
25
+ scrollable.style.setProperty('scroll-behavior', 'unset');
26
+ },
15
27
  onDrop: ({
16
28
  location,
17
29
  source
18
30
  }) => {
31
+ var _api$core;
32
+ scrollable.style.setProperty('scroll-behavior', null);
33
+ api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
34
+ tr
35
+ }) => {
36
+ return tr.setMeta(key, {
37
+ isDragging: false
38
+ });
39
+ });
19
40
  // if no drop targets are rendered, assume that drop is invalid
20
41
  if (location.current.dropTargets.length === 0) {
21
- var _api$core;
42
+ var _api$core2;
22
43
  const {
23
44
  start
24
45
  } = source.data;
25
- api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
46
+ api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.execute(({
26
47
  tr
27
48
  }) => {
28
49
  var _api$analytics;
@@ -42,7 +63,8 @@ const destroyFn = api => {
42
63
  });
43
64
  }
44
65
  }
45
- });
66
+ }));
67
+ return combine(...cleanupFn);
46
68
  };
47
69
  const initialState = {
48
70
  decorations: DecorationSet.empty,
@@ -50,8 +72,6 @@ const initialState = {
50
72
  activeNode: null,
51
73
  isDragging: false,
52
74
  isMenuOpen: false,
53
- start: null,
54
- end: null,
55
75
  editorHeight: 0,
56
76
  isResizerResizing: false,
57
77
  isDocSizeLimitEnabled: false
@@ -68,7 +88,7 @@ export const createPlugin = api => {
68
88
  return initialState;
69
89
  },
70
90
  apply(tr, currentState, oldState, newState) {
71
- var _decorationState, _meta$activeNode, _meta$isDragging, _meta$editorHeight, _isResizerResizing;
91
+ var _decorationState, _meta$activeNode, _meta$isDragging, _meta$editorHeight;
72
92
  if (initialState.isDocSizeLimitEnabled && newState.doc.nodeSize > DRAG_AND_DROP_DOC_SIZE_LIMIT) {
73
93
  return initialState;
74
94
  }
@@ -85,28 +105,30 @@ export const createPlugin = api => {
85
105
  // If tables or media are being resized, we want to hide the drag handle
86
106
  const resizerMeta = tr.getMeta('is-resizer-resizing');
87
107
  isResizerResizing = resizerMeta !== null && resizerMeta !== void 0 ? resizerMeta : isResizerResizing;
88
- const isEmptyDoc = newState.doc.childCount === 1 && newState.doc.nodeSize <= 4;
89
-
90
- // This is not targeted enough - it's trying to catch events like expand being set to breakout
91
- const maybeWidthUpdated = tr.docChanged && oldState.doc.nodeSize === newState.doc.nodeSize && oldState.doc.childCount === newState.doc.childCount;
92
108
  const nodeCountChanged = oldState.doc.childCount !== newState.doc.childCount;
93
109
 
94
110
  // During resize, remove the drag handle widget
95
- if (isResizerResizing || nodeCountChanged) {
111
+ if (isResizerResizing || nodeCountChanged || meta !== null && meta !== void 0 && meta.nodeMoved) {
96
112
  const oldHandle = decorations.find().filter(({
97
113
  spec
98
114
  }) => spec.id === 'drag-handle');
99
115
  decorations = decorations.remove(oldHandle);
100
116
  }
101
117
 
118
+ // This is not targeted enough - it's trying to catch events like expand being set to breakout
119
+ const maybeWidthUpdated = tr.docChanged && oldState.doc.nodeSize === newState.doc.nodeSize && !nodeCountChanged;
120
+ const redrawDecorations = decorations === DecorationSet.empty || (meta === null || meta === void 0 ? void 0 : meta.editorHeight) !== undefined && (meta === null || meta === void 0 ? void 0 : meta.editorHeight) !== editorHeight || maybeWidthUpdated || nodeCountChanged || resizerMeta === false || !!(meta !== null && meta !== void 0 && meta.nodeMoved) && tr.docChanged;
121
+
102
122
  // Draw node and mouseWrapper decorations at top level node if decorations is empty, editor height changes or node is moved
103
- const redrawDecorations = decorations === DecorationSet.empty || (meta === null || meta === void 0 ? void 0 : meta.editorHeight) !== undefined && (meta === null || meta === void 0 ? void 0 : meta.editorHeight) !== editorHeight || maybeWidthUpdated || nodeCountChanged || resizerMeta === false || (meta === null || meta === void 0 ? void 0 : meta.nodeMoved) && tr.docChanged;
104
- if (redrawDecorations && isResizerResizing === false && api) {
123
+ if (redrawDecorations && !isResizerResizing && api) {
105
124
  decorations = DecorationSet.create(newState.doc, []);
106
125
  const nodeDecs = nodeDecorations(newState);
107
126
  const mouseWrapperDecs = mouseMoveWrapperDecorations(newState, api);
108
127
  decorations = decorations.add(newState.doc, [...nodeDecs, ...mouseWrapperDecs]);
109
- if (activeNode) {
128
+
129
+ // Note: Quite often the handle is not in the right position after a node is moved
130
+ // it is safer for now to not show it when a node is moved
131
+ if (activeNode && !(meta !== null && meta !== void 0 && meta.nodeMoved)) {
110
132
  const draghandleDec = dragHandleDecoration(activeNode.pos, activeNode.anchorName, activeNode.nodeType, api);
111
133
  decorations = decorations.add(newState.doc, [draghandleDec]);
112
134
  }
@@ -164,6 +186,7 @@ export const createPlugin = api => {
164
186
  anchorName: activeNode.anchorName,
165
187
  nodeType: activeNode.nodeType
166
188
  } : activeNode;
189
+ const isEmptyDoc = newState.doc.childCount === 1 && newState.doc.nodeSize <= 4;
167
190
  return {
168
191
  decorations,
169
192
  decorationState: (_decorationState = decorationState) !== null && _decorationState !== void 0 ? _decorationState : currentState.decorationState,
@@ -171,7 +194,7 @@ export const createPlugin = api => {
171
194
  isDragging: (_meta$isDragging = meta === null || meta === void 0 ? void 0 : meta.isDragging) !== null && _meta$isDragging !== void 0 ? _meta$isDragging : currentState.isDragging,
172
195
  isMenuOpen: meta !== null && meta !== void 0 && meta.toggleMenu ? !isMenuOpen : isMenuOpen,
173
196
  editorHeight: (_meta$editorHeight = meta === null || meta === void 0 ? void 0 : meta.editorHeight) !== null && _meta$editorHeight !== void 0 ? _meta$editorHeight : currentState.editorHeight,
174
- isResizerResizing: (_isResizerResizing = isResizerResizing) !== null && _isResizerResizing !== void 0 ? _isResizerResizing : isResizerResizing,
197
+ isResizerResizing: isResizerResizing,
175
198
  isDocSizeLimitEnabled: initialState.isDocSizeLimitEnabled
176
199
  };
177
200
  }
@@ -204,10 +227,13 @@ export const createPlugin = api => {
204
227
 
205
228
  // Start observing the editor DOM element
206
229
  resizeObserver.observe(dom);
230
+
231
+ // Start pragmatic monitors
232
+ const pragmaticCleanup = destroyFn(api);
207
233
  return {
208
234
  destroy() {
209
235
  resizeObserver.unobserve(dom);
210
- return destroyFn(api);
236
+ pragmaticCleanup();
211
237
  }
212
238
  };
213
239
  }