@atlaskit/editor-plugin-block-controls 2.23.0 → 2.24.0

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.
@@ -8,6 +8,7 @@ import { isFragmentOfType, containsNodeOfType } from '../pm-plugins/utils/check-
8
8
  import { maxLayoutColumnSupported } from '../pm-plugins/utils/consts';
9
9
  import { fireInsertLayoutAnalytics, attachMoveNodeAnalytics } from '../pm-plugins/utils/fire-analytics';
10
10
  import { removeFromSource } from '../pm-plugins/utils/remove-from-source';
11
+ import { getMultiSelectionIfPosInside } from '../pm-plugins/utils/selection';
11
12
  import { updateColumnWidths } from '../pm-plugins/utils/update-column-widths';
12
13
  import { isInSameLayout } from '../pm-plugins/utils/validation';
13
14
  import { DEFAULT_COLUMN_DISTRIBUTIONS } from '../ui/consts';
@@ -159,7 +160,7 @@ const insertToDestination = (tr, to, sourceContent, toLayout, toLayoutPos) => {
159
160
  * Check if the node at `from` can be moved to node at `to` to create/expand a layout.
160
161
  * Returns the source and destination nodes and positions if it's a valid move, otherwise, undefined
161
162
  */
162
- const canMoveToLayout = (from, to, tr) => {
163
+ const canMoveToLayout = (api, from, to, tr) => {
163
164
  if (from === to) {
164
165
  return;
165
166
  }
@@ -192,17 +193,13 @@ const canMoveToLayout = (from, to, tr) => {
192
193
  let sourceFrom = from;
193
194
  let sourceTo = from + sourceContent.nodeSize;
194
195
  if (isMultiSelect) {
195
- var _tr$selection$$from$n;
196
- // Selection often starts from the content of the node (e.g. to show text selection properly),
197
- // so we need to trace back to the start of the node instead
198
- const contentStartPos = tr.selection.$from.nodeAfter && ((_tr$selection$$from$n = tr.selection.$from.nodeAfter) === null || _tr$selection$$from$n === void 0 ? void 0 : _tr$selection$$from$n.type.name) !== 'text' ? tr.selection.$from.pos : tr.selection.$from.before();
199
-
200
- // If the handle position sits within the Editor selection, we will move all nodes that sit in that selection
201
- // handle position is the same as `from` position
202
- const useSelection = from >= contentStartPos && from <= tr.selection.to;
203
- if (useSelection) {
204
- sourceFrom = contentStartPos;
205
- sourceTo = tr.selection.to;
196
+ const {
197
+ anchor,
198
+ head
199
+ } = getMultiSelectionIfPosInside(api, from);
200
+ if (anchor && head) {
201
+ sourceFrom = Math.min(anchor, head);
202
+ sourceTo = Math.max(anchor, head);
206
203
  sourceContent = tr.doc.slice(sourceFrom, sourceTo).content;
207
204
 
208
205
  // TODO: this might become expensive for large content, consider removing it if check has been done beforehand
@@ -280,7 +277,10 @@ const getBreakoutMode = (content, breakout) => {
280
277
  export const moveToLayout = api => (from, to, options) => ({
281
278
  tr
282
279
  }) => {
283
- const canMove = canMoveToLayout(from, to, tr);
280
+ if (!api) {
281
+ return tr;
282
+ }
283
+ const canMove = canMoveToLayout(api, from, to, tr);
284
284
  if (!canMove) {
285
285
  return tr;
286
286
  }
@@ -73,6 +73,23 @@ const destroyFn = (api, editorView) => {
73
73
  api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
74
74
  tr
75
75
  }) => {
76
+ if (editorExperiment('platform_editor_element_drag_and_drop_multiselect', true)) {
77
+ var _api$blockControls, _api$selection;
78
+ const {
79
+ multiSelectDnD
80
+ } = (api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.sharedState.currentState()) || {};
81
+ // Restore the users initial Editor selection when the drop completes
82
+ if (multiSelectDnD) {
83
+ // If the TextSelection between the drag start and end has changed, the document has changed, and we should not reapply the last selection
84
+ const expandedSelectionUnchanged = multiSelectDnD.textAnchor === tr.selection.anchor && multiSelectDnD.textHead === tr.selection.head;
85
+ if (expandedSelectionUnchanged) {
86
+ tr.setSelection(TextSelection.create(tr.doc, multiSelectDnD.userAnchor, multiSelectDnD.userHead));
87
+ }
88
+ }
89
+ api === null || api === void 0 ? void 0 : (_api$selection = api.selection) === null || _api$selection === void 0 ? void 0 : _api$selection.commands.clearManualSelection()({
90
+ tr
91
+ });
92
+ }
76
93
  const {
77
94
  start
78
95
  } = source.data;
@@ -111,14 +128,16 @@ const initialState = {
111
128
  editorWidthRight: 0,
112
129
  isResizerResizing: false,
113
130
  isDocSizeLimitEnabled: null,
114
- isPMDragging: false
131
+ isPMDragging: false,
132
+ multiSelectDnD: undefined
115
133
  };
116
134
  export const newApply = (api, formatMessage, tr, currentState, newState, flags, nodeViewPortalProviderAPI, anchorRectCache) => {
117
- var _meta$activeNode, _activeNode, _activeNode2, _meta$activeNode$hand, _meta$isDragging, _meta$isDragging2, _meta$editorHeight, _meta$editorWidthLeft, _meta$editorWidthRigh, _meta$isPMDragging;
135
+ var _meta$activeNode, _activeNode, _activeNode2, _meta$activeNode$hand, _meta$isDragging, _meta$isDragging2, _meta$editorHeight, _meta$editorWidthLeft, _meta$editorWidthRigh, _meta$isPMDragging, _meta$multiSelectDnD;
118
136
  let {
119
137
  activeNode,
120
138
  decorations,
121
- isResizerResizing
139
+ isResizerResizing,
140
+ multiSelectDnD
122
141
  } = currentState;
123
142
  const {
124
143
  editorHeight,
@@ -127,11 +146,11 @@ export const newApply = (api, formatMessage, tr, currentState, newState, flags,
127
146
  isDragging,
128
147
  isMenuOpen,
129
148
  // NOT USED
130
- isPMDragging // NOT USED
149
+ isPMDragging
131
150
  } = currentState;
132
151
  let isActiveNodeDeleted = false;
133
152
 
134
- // Remap existing decorations and activeNode when steps exist
153
+ // When steps exist, remap existing decorations, activeNode and multi select positions
135
154
  if (tr.docChanged) {
136
155
  decorations = decorations.map(tr.mapping, tr.doc);
137
156
  if (activeNode) {
@@ -143,10 +162,17 @@ export const newApply = (api, formatMessage, tr, currentState, newState, flags,
143
162
  nodeType: activeNode.nodeType
144
163
  };
145
164
  }
165
+ if (multiSelectDnD && flags.isMultiSelectEnabled) {
166
+ multiSelectDnD.anchor = tr.mapping.map(multiSelectDnD.anchor);
167
+ multiSelectDnD.head = tr.mapping.map(multiSelectDnD.head);
168
+ }
146
169
  }
147
170
  const meta = tr.getMeta(key);
148
171
  const resizerMeta = tr.getMeta('is-resizer-resizing');
149
172
  isResizerResizing = resizerMeta !== null && resizerMeta !== void 0 ? resizerMeta : isResizerResizing;
173
+ if (multiSelectDnD && flags.isMultiSelectEnabled) {
174
+ multiSelectDnD = (meta === null || meta === void 0 ? void 0 : meta.isDragging) === false ? undefined : multiSelectDnD;
175
+ }
150
176
  const {
151
177
  from,
152
178
  to,
@@ -245,7 +271,8 @@ export const newApply = (api, formatMessage, tr, currentState, newState, flags,
245
271
  editorWidthRight: (_meta$editorWidthRigh = meta === null || meta === void 0 ? void 0 : meta.editorWidthRight) !== null && _meta$editorWidthRigh !== void 0 ? _meta$editorWidthRigh : editorWidthRight,
246
272
  isResizerResizing: isResizerResizing,
247
273
  isDocSizeLimitEnabled: initialState.isDocSizeLimitEnabled,
248
- isPMDragging: (_meta$isPMDragging = meta === null || meta === void 0 ? void 0 : meta.isPMDragging) !== null && _meta$isPMDragging !== void 0 ? _meta$isPMDragging : isPMDragging
274
+ isPMDragging: (_meta$isPMDragging = meta === null || meta === void 0 ? void 0 : meta.isPMDragging) !== null && _meta$isPMDragging !== void 0 ? _meta$isPMDragging : isPMDragging,
275
+ multiSelectDnD: (_meta$multiSelectDnD = meta === null || meta === void 0 ? void 0 : meta.multiSelectDnD) !== null && _meta$multiSelectDnD !== void 0 ? _meta$multiSelectDnD : multiSelectDnD
249
276
  };
250
277
  };
251
278
  export const oldApply = (api, formatMessage, tr, currentState, oldState, newState, flags, nodeViewPortalProviderAPI, anchorRectCache) => {
@@ -401,8 +428,10 @@ export const createPlugin = (api, getIntl, nodeViewPortalProviderAPI) => {
401
428
  const isAdvancedLayoutEnabled = editorExperiment('advanced_layouts', true, {
402
429
  exposure: true
403
430
  });
431
+ const isMultiSelectEnabled = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true);
404
432
  const flags = {
405
- isNestedEnabled
433
+ isNestedEnabled,
434
+ isMultiSelectEnabled
406
435
  };
407
436
  let anchorRectCache;
408
437
  if (!isAnchorSupported()) {
@@ -467,8 +496,10 @@ export const createPlugin = (api, getIntl, nodeViewPortalProviderAPI) => {
467
496
  const domPos = Math.max(view.posAtDOM(nodeElement, 0) - 1, 0);
468
497
  const nodeTarget = state.doc.nodeAt(domPos);
469
498
  const isSameNode = !!(nodeTarget && draggable !== null && draggable !== void 0 && draggable.eq(nodeTarget));
470
- if (isSameNode) {
471
- // Prevent the default drop behavior if the position is within the activeNode
499
+ const isInSelection = domPos >= state.selection.$from.pos && domPos < state.selection.$to.pos;
500
+
501
+ // Prevent the default drop behavior if the position is within the activeNode or Editor selection
502
+ if (isSameNode || isInSelection && isMultiSelectEnabled) {
472
503
  event.preventDefault();
473
504
  return true;
474
505
  }
@@ -0,0 +1,17 @@
1
+ export const getMultiSelectionIfPosInside = (api, pos) => {
2
+ var _api$blockControls;
3
+ const {
4
+ multiSelectDnD
5
+ } = (api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.sharedState.currentState()) || {};
6
+ if (multiSelectDnD && multiSelectDnD.anchor >= 0 && multiSelectDnD.head >= 0) {
7
+ const multiFrom = Math.min(multiSelectDnD.anchor, multiSelectDnD.head);
8
+ const multiTo = Math.max(multiSelectDnD.anchor, multiSelectDnD.head);
9
+
10
+ // We subtract one as the handle position is before the node
11
+ return pos >= multiFrom - 1 && pos <= multiTo ? {
12
+ anchor: multiSelectDnD.anchor,
13
+ head: multiSelectDnD.head
14
+ } : {};
15
+ }
16
+ return {};
17
+ };
@@ -240,38 +240,26 @@ export const DragHandle = ({
240
240
  api === null || api === void 0 ? void 0 : (_api$core5 = api.core) === null || _api$core5 === void 0 ? void 0 : _api$core5.actions.execute(({
241
241
  tr
242
242
  }) => {
243
- var _api$blockControls, _api$analytics3;
243
+ var _api$blockControls2, _api$analytics3;
244
244
  const isMultiSelect = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true, {
245
245
  exposure: true
246
246
  });
247
- let selectionStart = start;
248
247
  if (isMultiSelect) {
249
- const selection = tr.selection;
250
- const selectionFrom = selection.$from.pos;
251
- const selectionTo = selection.$to.pos;
252
- const $selectionFrom = tr.doc.resolve(selectionFrom);
253
- const $selectionTo = tr.doc.resolve(selectionTo);
254
- selectionStart = $selectionFrom.start();
255
- const selectionEnd = $selectionTo.end();
256
248
  const handlePos = getPos();
257
249
  if (typeof handlePos !== 'number') {
258
250
  return tr;
259
251
  }
260
- const posBeforeNode = $selectionFrom.pos ? $selectionFrom.start() - 1 : $selectionFrom.pos;
261
- const shouldExpandSelection = handlePos >= posBeforeNode && handlePos <= selectionEnd;
262
- if (shouldExpandSelection) {
263
- //TODO: What happens if not a text selection?
264
- const newSelection = TextSelection.create(tr.doc, selectionStart, selectionEnd);
265
- tr.setSelection(newSelection);
266
- } else {
267
- const $selectionFrom = tr.doc.resolve(handlePos + 1);
268
- selectNode(tr, handlePos, $selectionFrom.node().type.name);
252
+ if (!tr.selection.empty && handlePos >= tr.selection.$from.start() - 1 && handlePos <= tr.selection.to) {
253
+ var _api$blockControls;
254
+ api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.commands.setMultiSelectPositions()({
255
+ tr
256
+ });
269
257
  }
270
258
  }
271
- api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.commands.setNodeDragged(getPos, anchorName, nodeType)({
259
+ api === null || api === void 0 ? void 0 : (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 ? void 0 : _api$blockControls2.commands.setNodeDragged(getPos, anchorName, nodeType)({
272
260
  tr
273
261
  });
274
- const resolvedMovingNode = tr.doc.resolve(selectionStart);
262
+ const resolvedMovingNode = tr.doc.resolve(start);
275
263
  const maybeNode = resolvedMovingNode.nodeAfter;
276
264
  tr.setMeta('scrollIntoView', false);
277
265
  api === null || api === void 0 ? void 0 : (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : _api$analytics3.actions.attachAnalyticsEvent({
@@ -416,6 +404,9 @@ export const DragHandle = ({
416
404
  const message = helpDescriptors.map(descriptor => {
417
405
  return descriptor.keymap ? [descriptor.description, getAriaKeyshortcuts(descriptor.keymap)] : [descriptor.description];
418
406
  }).join('. ');
407
+ const handleOnDrop = event => {
408
+ editorExperiment('platform_editor_element_drag_and_drop_multiselect', true) && event.stopPropagation();
409
+ };
419
410
  const renderButton = () =>
420
411
  // eslint-disable-next-line @atlaskit/design-system/no-html-button
421
412
  jsx("button", {
@@ -430,7 +421,10 @@ export const DragHandle = ({
430
421
  style: positionStyles,
431
422
  onClick: handleOnClick,
432
423
  onMouseDown: handleMouseDown,
433
- onKeyDown: handleKeyDown,
424
+ onKeyDown: handleKeyDown
425
+ // eslint-disable-next-line @atlaskit/design-system/no-direct-use-of-web-platform-drag-and-drop
426
+ ,
427
+ onDrop: handleOnDrop,
434
428
  "data-testid": "block-ctrl-drag-handle"
435
429
  }, jsx(Box, {
436
430
  as: "span",
@@ -1,4 +1,9 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
1
4
  import React from 'react';
5
+ import { expandSelectionBounds } from '@atlaskit/editor-common/selection';
6
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
2
7
  import { moveNode } from './editor-commands/move-node';
3
8
  import { moveToLayout } from './editor-commands/move-to-layout';
4
9
  import { createPlugin, key } from './pm-plugins/main';
@@ -24,14 +29,15 @@ export var blockControlsPlugin = function blockControlsPlugin(_ref) {
24
29
  showDragHandleAt: function showDragHandleAt(pos, anchorName, nodeType, handleOptions) {
25
30
  return function (_ref3) {
26
31
  var tr = _ref3.tr;
27
- tr.setMeta(key, {
32
+ var currMeta = tr.getMeta(key);
33
+ tr.setMeta(key, _objectSpread(_objectSpread({}, currMeta), {}, {
28
34
  activeNode: {
29
35
  pos: pos,
30
36
  anchorName: anchorName,
31
37
  nodeType: nodeType,
32
38
  handleOptions: handleOptions
33
39
  }
34
- });
40
+ }));
35
41
  return tr;
36
42
  };
37
43
  },
@@ -42,20 +48,52 @@ export var blockControlsPlugin = function blockControlsPlugin(_ref) {
42
48
  if (pos === undefined) {
43
49
  return tr;
44
50
  }
45
- tr.setMeta(key, {
51
+ var currMeta = tr.getMeta(key);
52
+ tr.setMeta(key, _objectSpread(_objectSpread({}, currMeta), {}, {
46
53
  isDragging: true,
47
54
  activeNode: {
48
55
  pos: pos,
49
56
  anchorName: anchorName,
50
57
  nodeType: nodeType
51
58
  }
59
+ }));
60
+ return tr;
61
+ };
62
+ },
63
+ setMultiSelectPositions: function setMultiSelectPositions() {
64
+ return function (_ref5) {
65
+ var _api$selection;
66
+ var tr = _ref5.tr;
67
+ var _tr$selection = tr.selection,
68
+ userAnchor = _tr$selection.anchor,
69
+ userHead = _tr$selection.head;
70
+ var _expandSelectionBound = expandSelectionBounds(tr.selection.$anchor, tr.selection.$head),
71
+ expandedAnchor = _expandSelectionBound.$anchor,
72
+ expandedHead = _expandSelectionBound.$head;
73
+ api === null || api === void 0 || (_api$selection = api.selection) === null || _api$selection === void 0 || _api$selection.commands.setManualSelection(expandedAnchor.pos, expandedHead.pos)({
74
+ tr: tr
52
75
  });
76
+ // this is to normalise the selection's boundaries to inline positions, preventing it from collapsing
77
+ var expandedNormalisedSel = TextSelection.between(expandedAnchor, expandedHead);
78
+ tr.setSelection(expandedNormalisedSel);
79
+ var multiSelectDnD = {
80
+ anchor: expandedAnchor.pos,
81
+ head: expandedHead.pos,
82
+ textAnchor: expandedNormalisedSel.anchor,
83
+ textHead: expandedNormalisedSel.head,
84
+ userAnchor: userAnchor,
85
+ userHead: userHead
86
+ };
87
+ var currMeta = tr.getMeta(key);
88
+ tr.setMeta(key, _objectSpread(_objectSpread({}, currMeta), {}, {
89
+ multiSelectDnD: multiSelectDnD
90
+ }));
53
91
  return tr;
54
92
  };
55
93
  }
56
94
  },
57
95
  getSharedState: function getSharedState(editorState) {
58
- var _key$getState$isMenuO, _key$getState, _key$getState$activeN, _key$getState2, _key$getState$isDragg, _key$getState3, _key$getState$isPMDra, _key$getState4;
96
+ var _key$getState$isMenuO, _key$getState, _key$getState$activeN, _key$getState2, _key$getState$isDragg, _key$getState3, _key$getState$isPMDra, _key$getState4, _key$getState$multiSe, _key$getState5;
59
97
  if (!editorState) {
60
98
  return undefined;
61
99
  }
@@ -63,7 +101,8 @@ export var blockControlsPlugin = function blockControlsPlugin(_ref) {
63
101
  isMenuOpen: (_key$getState$isMenuO = (_key$getState = key.getState(editorState)) === null || _key$getState === void 0 ? void 0 : _key$getState.isMenuOpen) !== null && _key$getState$isMenuO !== void 0 ? _key$getState$isMenuO : false,
64
102
  activeNode: (_key$getState$activeN = (_key$getState2 = key.getState(editorState)) === null || _key$getState2 === void 0 ? void 0 : _key$getState2.activeNode) !== null && _key$getState$activeN !== void 0 ? _key$getState$activeN : undefined,
65
103
  isDragging: (_key$getState$isDragg = (_key$getState3 = key.getState(editorState)) === null || _key$getState3 === void 0 ? void 0 : _key$getState3.isDragging) !== null && _key$getState$isDragg !== void 0 ? _key$getState$isDragg : false,
66
- isPMDragging: (_key$getState$isPMDra = (_key$getState4 = key.getState(editorState)) === null || _key$getState4 === void 0 ? void 0 : _key$getState4.isPMDragging) !== null && _key$getState$isPMDra !== void 0 ? _key$getState$isPMDra : false
104
+ isPMDragging: (_key$getState$isPMDra = (_key$getState4 = key.getState(editorState)) === null || _key$getState4 === void 0 ? void 0 : _key$getState4.isPMDragging) !== null && _key$getState$isPMDra !== void 0 ? _key$getState$isPMDra : false,
105
+ multiSelectDnD: (_key$getState$multiSe = (_key$getState5 = key.getState(editorState)) === null || _key$getState5 === void 0 ? void 0 : _key$getState5.multiSelectDnD) !== null && _key$getState$multiSe !== void 0 ? _key$getState$multiSe : undefined
67
106
  };
68
107
  },
69
108
  contentComponent: function contentComponent() {
@@ -19,6 +19,7 @@ import { attachMoveNodeAnalytics } from '../pm-plugins/utils/fire-analytics';
19
19
  import { getNestedNodePosition } from '../pm-plugins/utils/getNestedNodePosition';
20
20
  import { selectNode, setCursorPositionAtMovedNode } from '../pm-plugins/utils/getSelection';
21
21
  import { removeFromSource } from '../pm-plugins/utils/remove-from-source';
22
+ import { getMultiSelectionIfPosInside } from '../pm-plugins/utils/selection';
22
23
  import { canMoveNodeToIndex, isInsideTable, transformSliceExpandToNestedExpand } from '../pm-plugins/utils/validation';
23
24
 
24
25
  /**
@@ -130,7 +131,8 @@ export var moveNodeViaShortcut = function moveNodeViaShortcut(api, direction, fo
130
131
 
131
132
  // if the current node is the last node, don't do anything
132
133
  if (_index >= parent.childCount - 1) {
133
- return false;
134
+ // prevent event propagation to avoid moving the cursor and still select the node
135
+ return true;
134
136
  }
135
137
  var moveToEnd = _index === parent.childCount - 2;
136
138
  moveToPos = moveToEnd ? $pos.before() : selection.to + ((nextNode === null || nextNode === void 0 ? void 0 : nextNode.nodeSize) || 1);
@@ -213,26 +215,28 @@ export var moveNode = function moveNode(api) {
213
215
  var formatMessage = arguments.length > 3 ? arguments[3] : undefined;
214
216
  return function (_ref6) {
215
217
  var tr = _ref6.tr;
216
- var isMultiSelect = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true, {
217
- exposure: true
218
- });
219
- var selection = tr.selection;
220
- var selectionFrom = selection.$from.pos;
221
- var selectionTo = selection.$to.pos;
218
+ if (!api) {
219
+ return tr;
220
+ }
222
221
  var handleNode = tr.doc.nodeAt(start);
223
222
  if (!handleNode) {
224
223
  return tr;
225
224
  }
226
225
  var sliceFrom = start;
227
226
  var sliceTo;
227
+ var isMultiSelect = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true, {
228
+ exposure: true
229
+ });
228
230
  if (isMultiSelect) {
229
231
  var _handleNode$nodeSize;
230
- // //If the handle position sits within the Editor selection, we will move all nodes that sit in that selection
231
- var useSelection = sliceFrom >= selectionFrom - 1 && sliceFrom <= selectionTo;
232
- sliceFrom = useSelection ? selectionFrom : start;
232
+ var _getMultiSelectionIfP = getMultiSelectionIfPosInside(api, start),
233
+ anchor = _getMultiSelectionIfP.anchor,
234
+ head = _getMultiSelectionIfP.head;
235
+ var inSelection = anchor !== undefined && head !== undefined;
236
+ sliceFrom = inSelection ? Math.min(anchor, head) : start;
233
237
  var handleSize = (_handleNode$nodeSize = handleNode === null || handleNode === void 0 ? void 0 : handleNode.nodeSize) !== null && _handleNode$nodeSize !== void 0 ? _handleNode$nodeSize : 1;
234
238
  var handleEnd = sliceFrom + handleSize;
235
- sliceTo = useSelection ? selectionTo : handleEnd;
239
+ sliceTo = inSelection ? Math.max(anchor, head) : handleEnd;
236
240
  } else {
237
241
  var _handleNode$nodeSize2;
238
242
  var size = (_handleNode$nodeSize2 = handleNode === null || handleNode === void 0 ? void 0 : handleNode.nodeSize) !== null && _handleNode$nodeSize2 !== void 0 ? _handleNode$nodeSize2 : 1;
@@ -8,6 +8,7 @@ import { isFragmentOfType, containsNodeOfType } from '../pm-plugins/utils/check-
8
8
  import { maxLayoutColumnSupported } from '../pm-plugins/utils/consts';
9
9
  import { fireInsertLayoutAnalytics, attachMoveNodeAnalytics } from '../pm-plugins/utils/fire-analytics';
10
10
  import { removeFromSource } from '../pm-plugins/utils/remove-from-source';
11
+ import { getMultiSelectionIfPosInside } from '../pm-plugins/utils/selection';
11
12
  import { updateColumnWidths } from '../pm-plugins/utils/update-column-widths';
12
13
  import { isInSameLayout } from '../pm-plugins/utils/validation';
13
14
  import { DEFAULT_COLUMN_DISTRIBUTIONS } from '../ui/consts';
@@ -155,7 +156,7 @@ var insertToDestination = function insertToDestination(tr, to, sourceContent, to
155
156
  * Check if the node at `from` can be moved to node at `to` to create/expand a layout.
156
157
  * Returns the source and destination nodes and positions if it's a valid move, otherwise, undefined
157
158
  */
158
- var canMoveToLayout = function canMoveToLayout(from, to, tr) {
159
+ var canMoveToLayout = function canMoveToLayout(api, from, to, tr) {
159
160
  if (from === to) {
160
161
  return;
161
162
  }
@@ -187,17 +188,12 @@ var canMoveToLayout = function canMoveToLayout(from, to, tr) {
187
188
  var sourceFrom = from;
188
189
  var sourceTo = from + sourceContent.nodeSize;
189
190
  if (isMultiSelect) {
190
- var _tr$selection$$from$n;
191
- // Selection often starts from the content of the node (e.g. to show text selection properly),
192
- // so we need to trace back to the start of the node instead
193
- var contentStartPos = tr.selection.$from.nodeAfter && ((_tr$selection$$from$n = tr.selection.$from.nodeAfter) === null || _tr$selection$$from$n === void 0 ? void 0 : _tr$selection$$from$n.type.name) !== 'text' ? tr.selection.$from.pos : tr.selection.$from.before();
194
-
195
- // If the handle position sits within the Editor selection, we will move all nodes that sit in that selection
196
- // handle position is the same as `from` position
197
- var useSelection = from >= contentStartPos && from <= tr.selection.to;
198
- if (useSelection) {
199
- sourceFrom = contentStartPos;
200
- sourceTo = tr.selection.to;
191
+ var _getMultiSelectionIfP = getMultiSelectionIfPosInside(api, from),
192
+ anchor = _getMultiSelectionIfP.anchor,
193
+ head = _getMultiSelectionIfP.head;
194
+ if (anchor && head) {
195
+ sourceFrom = Math.min(anchor, head);
196
+ sourceTo = Math.max(anchor, head);
201
197
  sourceContent = tr.doc.slice(sourceFrom, sourceTo).content;
202
198
 
203
199
  // TODO: this might become expensive for large content, consider removing it if check has been done beforehand
@@ -285,7 +281,10 @@ export var moveToLayout = function moveToLayout(api) {
285
281
  return function (from, to, options) {
286
282
  return function (_ref7) {
287
283
  var tr = _ref7.tr;
288
- var canMove = canMoveToLayout(from, to, tr);
284
+ if (!api) {
285
+ return tr;
286
+ }
287
+ var canMove = canMoveToLayout(api, from, to, tr);
289
288
  if (!canMove) {
290
289
  return tr;
291
290
  }
@@ -71,8 +71,24 @@ var destroyFn = function destroyFn(api, editorView) {
71
71
  }
72
72
  api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref3) {
73
73
  var tr = _ref3.tr;
74
- var _ref4 = source.data,
75
- start = _ref4.start;
74
+ if (editorExperiment('platform_editor_element_drag_and_drop_multiselect', true)) {
75
+ var _api$blockControls, _api$selection;
76
+ var _ref4 = (api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.sharedState.currentState()) || {},
77
+ multiSelectDnD = _ref4.multiSelectDnD;
78
+ // Restore the users initial Editor selection when the drop completes
79
+ if (multiSelectDnD) {
80
+ // If the TextSelection between the drag start and end has changed, the document has changed, and we should not reapply the last selection
81
+ var expandedSelectionUnchanged = multiSelectDnD.textAnchor === tr.selection.anchor && multiSelectDnD.textHead === tr.selection.head;
82
+ if (expandedSelectionUnchanged) {
83
+ tr.setSelection(TextSelection.create(tr.doc, multiSelectDnD.userAnchor, multiSelectDnD.userHead));
84
+ }
85
+ }
86
+ api === null || api === void 0 || (_api$selection = api.selection) === null || _api$selection === void 0 || _api$selection.commands.clearManualSelection()({
87
+ tr: tr
88
+ });
89
+ }
90
+ var _ref5 = source.data,
91
+ start = _ref5.start;
76
92
  // if no drop targets are rendered, assume that drop is invalid
77
93
  if (location.current.dropTargets.length === 0) {
78
94
  var _api$analytics2;
@@ -108,13 +124,15 @@ var initialState = {
108
124
  editorWidthRight: 0,
109
125
  isResizerResizing: false,
110
126
  isDocSizeLimitEnabled: null,
111
- isPMDragging: false
127
+ isPMDragging: false,
128
+ multiSelectDnD: undefined
112
129
  };
113
130
  export var newApply = function newApply(api, formatMessage, tr, currentState, newState, flags, nodeViewPortalProviderAPI, anchorRectCache) {
114
- var _meta$activeNode, _activeNode, _activeNode2, _meta$activeNode$hand, _meta$isDragging, _meta$isDragging2, _meta$editorHeight, _meta$editorWidthLeft, _meta$editorWidthRigh, _meta$isPMDragging;
131
+ var _meta$activeNode, _activeNode, _activeNode2, _meta$activeNode$hand, _meta$isDragging, _meta$isDragging2, _meta$editorHeight, _meta$editorWidthLeft, _meta$editorWidthRigh, _meta$isPMDragging, _meta$multiSelectDnD;
115
132
  var activeNode = currentState.activeNode,
116
133
  decorations = currentState.decorations,
117
- isResizerResizing = currentState.isResizerResizing;
134
+ isResizerResizing = currentState.isResizerResizing,
135
+ multiSelectDnD = currentState.multiSelectDnD;
118
136
  var editorHeight = currentState.editorHeight,
119
137
  editorWidthLeft = currentState.editorWidthLeft,
120
138
  editorWidthRight = currentState.editorWidthRight,
@@ -123,7 +141,7 @@ export var newApply = function newApply(api, formatMessage, tr, currentState, ne
123
141
  isPMDragging = currentState.isPMDragging;
124
142
  var isActiveNodeDeleted = false;
125
143
 
126
- // Remap existing decorations and activeNode when steps exist
144
+ // When steps exist, remap existing decorations, activeNode and multi select positions
127
145
  if (tr.docChanged) {
128
146
  decorations = decorations.map(tr.mapping, tr.doc);
129
147
  if (activeNode) {
@@ -135,10 +153,17 @@ export var newApply = function newApply(api, formatMessage, tr, currentState, ne
135
153
  nodeType: activeNode.nodeType
136
154
  };
137
155
  }
156
+ if (multiSelectDnD && flags.isMultiSelectEnabled) {
157
+ multiSelectDnD.anchor = tr.mapping.map(multiSelectDnD.anchor);
158
+ multiSelectDnD.head = tr.mapping.map(multiSelectDnD.head);
159
+ }
138
160
  }
139
161
  var meta = tr.getMeta(key);
140
162
  var resizerMeta = tr.getMeta('is-resizer-resizing');
141
163
  isResizerResizing = resizerMeta !== null && resizerMeta !== void 0 ? resizerMeta : isResizerResizing;
164
+ if (multiSelectDnD && flags.isMultiSelectEnabled) {
165
+ multiSelectDnD = (meta === null || meta === void 0 ? void 0 : meta.isDragging) === false ? undefined : multiSelectDnD;
166
+ }
142
167
  var _getTrMetadata = getTrMetadata(tr),
143
168
  from = _getTrMetadata.from,
144
169
  to = _getTrMetadata.to,
@@ -238,7 +263,8 @@ export var newApply = function newApply(api, formatMessage, tr, currentState, ne
238
263
  editorWidthRight: (_meta$editorWidthRigh = meta === null || meta === void 0 ? void 0 : meta.editorWidthRight) !== null && _meta$editorWidthRigh !== void 0 ? _meta$editorWidthRigh : editorWidthRight,
239
264
  isResizerResizing: isResizerResizing,
240
265
  isDocSizeLimitEnabled: initialState.isDocSizeLimitEnabled,
241
- isPMDragging: (_meta$isPMDragging = meta === null || meta === void 0 ? void 0 : meta.isPMDragging) !== null && _meta$isPMDragging !== void 0 ? _meta$isPMDragging : isPMDragging
266
+ isPMDragging: (_meta$isPMDragging = meta === null || meta === void 0 ? void 0 : meta.isPMDragging) !== null && _meta$isPMDragging !== void 0 ? _meta$isPMDragging : isPMDragging,
267
+ multiSelectDnD: (_meta$multiSelectDnD = meta === null || meta === void 0 ? void 0 : meta.multiSelectDnD) !== null && _meta$multiSelectDnD !== void 0 ? _meta$multiSelectDnD : multiSelectDnD
242
268
  };
243
269
  };
244
270
  export var oldApply = function oldApply(api, formatMessage, tr, currentState, oldState, newState, flags, nodeViewPortalProviderAPI, anchorRectCache) {
@@ -282,8 +308,8 @@ export var oldApply = function oldApply(api, formatMessage, tr, currentState, ol
282
308
  }
283
309
  var decsLength = isNestedEnabled ? decorations.find(undefined, undefined, function (spec) {
284
310
  return spec.type === 'node-decoration';
285
- }).length : decorations.find().filter(function (_ref5) {
286
- var spec = _ref5.spec;
311
+ }).length : decorations.find().filter(function (_ref6) {
312
+ var spec = _ref6.spec;
287
313
  return spec.type !== 'drag-handle';
288
314
  }).length;
289
315
  var isDecsMissing = false;
@@ -321,7 +347,7 @@ export var oldApply = function oldApply(api, formatMessage, tr, currentState, ol
321
347
  var newNodeDecs = nodeDecorations(newState);
322
348
  decorations = decorations.add(newState.doc, _toConsumableArray(newNodeDecs));
323
349
  if (activeNode && !(meta !== null && meta !== void 0 && meta.nodeMoved) && !isDecsMissing) {
324
- var _meta$activeNode$pos, _meta$activeNode3, _ref6, _meta$activeNode$anch, _meta$activeNode4, _decAtPos$spec, _ref7, _meta$activeNode$node, _meta$activeNode5, _decAtPos$spec2, _meta$activeNode6;
350
+ var _meta$activeNode$pos, _meta$activeNode3, _ref7, _meta$activeNode$anch, _meta$activeNode4, _decAtPos$spec, _ref8, _meta$activeNode$node, _meta$activeNode5, _decAtPos$spec2, _meta$activeNode6;
325
351
  var mappedPosisiton = tr.mapping.map(activeNode.pos);
326
352
  var prevMappedPos = oldState.tr.mapping.map(activeNode.pos);
327
353
 
@@ -340,7 +366,7 @@ export var oldApply = function oldApply(api, formatMessage, tr, currentState, ol
340
366
  var decAtPos = newNodeDecs.find(function (dec) {
341
367
  return dec.from === mappedPosisiton;
342
368
  });
343
- var draghandleDec = dragHandleDecoration(api, formatMessage, (_meta$activeNode$pos = meta === null || meta === void 0 || (_meta$activeNode3 = meta.activeNode) === null || _meta$activeNode3 === void 0 ? void 0 : _meta$activeNode3.pos) !== null && _meta$activeNode$pos !== void 0 ? _meta$activeNode$pos : mappedPosisiton, (_ref6 = (_meta$activeNode$anch = meta === null || meta === void 0 || (_meta$activeNode4 = meta.activeNode) === null || _meta$activeNode4 === void 0 ? void 0 : _meta$activeNode4.anchorName) !== null && _meta$activeNode$anch !== void 0 ? _meta$activeNode$anch : decAtPos === null || decAtPos === void 0 || (_decAtPos$spec = decAtPos.spec) === null || _decAtPos$spec === void 0 ? void 0 : _decAtPos$spec.anchorName) !== null && _ref6 !== void 0 ? _ref6 : activeNode === null || activeNode === void 0 ? void 0 : activeNode.anchorName, (_ref7 = (_meta$activeNode$node = meta === null || meta === void 0 || (_meta$activeNode5 = meta.activeNode) === null || _meta$activeNode5 === void 0 ? void 0 : _meta$activeNode5.nodeType) !== null && _meta$activeNode$node !== void 0 ? _meta$activeNode$node : decAtPos === null || decAtPos === void 0 || (_decAtPos$spec2 = decAtPos.spec) === null || _decAtPos$spec2 === void 0 ? void 0 : _decAtPos$spec2.nodeType) !== null && _ref7 !== void 0 ? _ref7 : activeNode === null || activeNode === void 0 ? void 0 : activeNode.nodeType, nodeViewPortalProviderAPI, meta === null || meta === void 0 || (_meta$activeNode6 = meta.activeNode) === null || _meta$activeNode6 === void 0 ? void 0 : _meta$activeNode6.handleOptions);
369
+ var draghandleDec = dragHandleDecoration(api, formatMessage, (_meta$activeNode$pos = meta === null || meta === void 0 || (_meta$activeNode3 = meta.activeNode) === null || _meta$activeNode3 === void 0 ? void 0 : _meta$activeNode3.pos) !== null && _meta$activeNode$pos !== void 0 ? _meta$activeNode$pos : mappedPosisiton, (_ref7 = (_meta$activeNode$anch = meta === null || meta === void 0 || (_meta$activeNode4 = meta.activeNode) === null || _meta$activeNode4 === void 0 ? void 0 : _meta$activeNode4.anchorName) !== null && _meta$activeNode$anch !== void 0 ? _meta$activeNode$anch : decAtPos === null || decAtPos === void 0 || (_decAtPos$spec = decAtPos.spec) === null || _decAtPos$spec === void 0 ? void 0 : _decAtPos$spec.anchorName) !== null && _ref7 !== void 0 ? _ref7 : activeNode === null || activeNode === void 0 ? void 0 : activeNode.anchorName, (_ref8 = (_meta$activeNode$node = meta === null || meta === void 0 || (_meta$activeNode5 = meta.activeNode) === null || _meta$activeNode5 === void 0 ? void 0 : _meta$activeNode5.nodeType) !== null && _meta$activeNode$node !== void 0 ? _meta$activeNode$node : decAtPos === null || decAtPos === void 0 || (_decAtPos$spec2 = decAtPos.spec) === null || _decAtPos$spec2 === void 0 ? void 0 : _decAtPos$spec2.nodeType) !== null && _ref8 !== void 0 ? _ref8 : activeNode === null || activeNode === void 0 ? void 0 : activeNode.nodeType, nodeViewPortalProviderAPI, meta === null || meta === void 0 || (_meta$activeNode6 = meta.activeNode) === null || _meta$activeNode6 === void 0 ? void 0 : _meta$activeNode6.handleOptions);
344
370
  decorations = decorations.add(newState.doc, [draghandleDec]);
345
371
  }
346
372
  }
@@ -412,8 +438,10 @@ export var createPlugin = function createPlugin(api, getIntl, nodeViewPortalProv
412
438
  var isAdvancedLayoutEnabled = editorExperiment('advanced_layouts', true, {
413
439
  exposure: true
414
440
  });
441
+ var isMultiSelectEnabled = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true);
415
442
  var flags = {
416
- isNestedEnabled: isNestedEnabled
443
+ isNestedEnabled: isNestedEnabled,
444
+ isMultiSelectEnabled: isMultiSelectEnabled
417
445
  };
418
446
  var anchorRectCache;
419
447
  if (!isAnchorSupported()) {
@@ -476,8 +504,10 @@ export var createPlugin = function createPlugin(api, getIntl, nodeViewPortalProv
476
504
  var domPos = Math.max(view.posAtDOM(nodeElement, 0) - 1, 0);
477
505
  var nodeTarget = state.doc.nodeAt(domPos);
478
506
  var isSameNode = !!(nodeTarget && draggable !== null && draggable !== void 0 && draggable.eq(nodeTarget));
479
- if (isSameNode) {
480
- // Prevent the default drop behavior if the position is within the activeNode
507
+ var isInSelection = domPos >= state.selection.$from.pos && domPos < state.selection.$to.pos;
508
+
509
+ // Prevent the default drop behavior if the position is within the activeNode or Editor selection
510
+ if (isSameNode || isInSelection && isMultiSelectEnabled) {
481
511
  event.preventDefault();
482
512
  return true;
483
513
  }
@@ -0,0 +1,16 @@
1
+ export var getMultiSelectionIfPosInside = function getMultiSelectionIfPosInside(api, pos) {
2
+ var _api$blockControls;
3
+ var _ref = (api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.sharedState.currentState()) || {},
4
+ multiSelectDnD = _ref.multiSelectDnD;
5
+ if (multiSelectDnD && multiSelectDnD.anchor >= 0 && multiSelectDnD.head >= 0) {
6
+ var multiFrom = Math.min(multiSelectDnD.anchor, multiSelectDnD.head);
7
+ var multiTo = Math.max(multiSelectDnD.anchor, multiSelectDnD.head);
8
+
9
+ // We subtract one as the handle position is before the node
10
+ return pos >= multiFrom - 1 && pos <= multiTo ? {
11
+ anchor: multiSelectDnD.anchor,
12
+ head: multiSelectDnD.head
13
+ } : {};
14
+ }
15
+ return {};
16
+ };