@atlaskit/editor-plugin-block-controls 8.0.7 → 8.0.9

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/blockControlsPlugin.js +4 -0
  3. package/dist/cjs/editor-commands/map-preserved-selection.js +30 -0
  4. package/dist/cjs/pm-plugins/selection-preservation/pm-plugin.js +16 -11
  5. package/dist/cjs/pm-plugins/selection-preservation/utils.js +2 -17
  6. package/dist/cjs/pm-plugins/utils/getSelection.js +11 -2
  7. package/dist/cjs/pm-plugins/utils/selection.js +45 -30
  8. package/dist/es2019/blockControlsPlugin.js +2 -0
  9. package/dist/es2019/editor-commands/map-preserved-selection.js +21 -0
  10. package/dist/es2019/pm-plugins/selection-preservation/pm-plugin.js +18 -13
  11. package/dist/es2019/pm-plugins/selection-preservation/utils.js +1 -16
  12. package/dist/es2019/pm-plugins/utils/getSelection.js +10 -1
  13. package/dist/es2019/pm-plugins/utils/selection.js +47 -32
  14. package/dist/esm/blockControlsPlugin.js +4 -0
  15. package/dist/esm/editor-commands/map-preserved-selection.js +24 -0
  16. package/dist/esm/pm-plugins/selection-preservation/pm-plugin.js +18 -13
  17. package/dist/esm/pm-plugins/selection-preservation/utils.js +1 -16
  18. package/dist/esm/pm-plugins/utils/getSelection.js +10 -1
  19. package/dist/esm/pm-plugins/utils/selection.js +45 -30
  20. package/dist/types/blockControlsPluginType.d.ts +1 -0
  21. package/dist/types/editor-commands/map-preserved-selection.d.ts +13 -0
  22. package/dist/types/pm-plugins/selection-preservation/utils.d.ts +1 -10
  23. package/dist/types/pm-plugins/utils/getSelection.d.ts +9 -0
  24. package/dist/types/pm-plugins/utils/selection.d.ts +15 -1
  25. package/dist/types-ts4.5/blockControlsPluginType.d.ts +1 -0
  26. package/dist/types-ts4.5/editor-commands/map-preserved-selection.d.ts +13 -0
  27. package/dist/types-ts4.5/pm-plugins/selection-preservation/utils.d.ts +1 -10
  28. package/dist/types-ts4.5/pm-plugins/utils/getSelection.d.ts +9 -0
  29. package/dist/types-ts4.5/pm-plugins/utils/selection.d.ts +15 -1
  30. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @atlaskit/editor-plugin-block-controls
2
2
 
3
+ ## 8.0.9
4
+
5
+ ### Patch Changes
6
+
7
+ - [`2a1bf10d70beb`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2a1bf10d70beb) -
8
+ EDITOR-4293 Fix block menu selection highlight issues
9
+
10
+ ## 8.0.8
11
+
12
+ ### Patch Changes
13
+
14
+ - [`eb7609ee331ab`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/eb7609ee331ab) -
15
+ [ux] EDITOR-4264 Fix preserved selection mapping
16
+ - Updated dependencies
17
+
3
18
  ## 8.0.7
4
19
 
5
20
  ### Patch Changes
@@ -14,6 +14,7 @@ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
14
14
  var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
15
15
  var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
16
16
  var _handleKeyDownWithPreservedSelection = require("./editor-commands/handle-key-down-with-preserved-selection");
17
+ var _mapPreservedSelection2 = require("./editor-commands/map-preserved-selection");
17
18
  var _moveNode = require("./editor-commands/move-node");
18
19
  var _moveNodeWithBlockMenu2 = require("./editor-commands/move-node-with-block-menu");
19
20
  var _moveToLayout = require("./editor-commands/move-to-layout");
@@ -244,6 +245,9 @@ var blockControlsPlugin = exports.blockControlsPlugin = function blockControlsPl
244
245
  }));
245
246
  };
246
247
  },
248
+ mapPreservedSelection: function mapPreservedSelection(mapping) {
249
+ return (0, _mapPreservedSelection2.mapPreservedSelection)(mapping);
250
+ },
247
251
  moveNodeWithBlockMenu: function moveNodeWithBlockMenu(direction) {
248
252
  return (0, _moveNodeWithBlockMenu2.moveNodeWithBlockMenu)(api, direction);
249
253
  },
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.mapPreservedSelection = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _main = require("../pm-plugins/main");
10
+ 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; }
11
+ 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) { (0, _defineProperty2.default)(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; }
12
+ /**
13
+ * Applies metadata to the transaction which can be used to apply custom mapping
14
+ * to the preserved selection.
15
+ *
16
+ * This can be used when nodes are transformed/moved in a way that natural mapping
17
+ * would not correctly update the preserved selection.
18
+ *
19
+ * @param preservedSelectionMapping The mapping to apply to the preserved selection.
20
+ * @returns An editor command that sets the preserved selection mapping in the transaction metadata.
21
+ */
22
+ var mapPreservedSelection = exports.mapPreservedSelection = function mapPreservedSelection(mapping) {
23
+ return function (_ref) {
24
+ var tr = _ref.tr;
25
+ var currMeta = tr.getMeta(_main.key);
26
+ return tr.setMeta(_main.key, _objectSpread(_objectSpread({}, currMeta), {}, {
27
+ preservedSelectionMapping: mapping
28
+ }));
29
+ };
30
+ };
@@ -6,9 +6,9 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.createSelectionPreservationPlugin = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
- var _monitoring = require("@atlaskit/editor-common/monitoring");
10
9
  var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
11
10
  var _styles = require("@atlaskit/editor-common/styles");
11
+ var _main = require("../main");
12
12
  var _selection = require("../utils/selection");
13
13
  var _editorCommands = require("./editor-commands");
14
14
  var _pluginKey = require("./plugin-key");
@@ -59,11 +59,10 @@ var createSelectionPreservationPlugin = exports.createSelectionPreservationPlugi
59
59
  var meta = (0, _utils.getSelectionPreservationMeta)(tr);
60
60
  var newState = _objectSpread({}, pluginState);
61
61
  if ((meta === null || meta === void 0 ? void 0 : meta.type) === 'startPreserving') {
62
- newState.preservedSelection = (0, _selection.mapPreservedSelection)(tr.selection, tr);
62
+ newState.preservedSelection = (0, _selection.createPreservedSelection)(tr.doc.resolve(tr.selection.from), tr.doc.resolve(tr.selection.to));
63
63
  } else if ((meta === null || meta === void 0 ? void 0 : meta.type) === 'stopPreserving') {
64
64
  newState.preservedSelection = undefined;
65
- }
66
- if (newState.preservedSelection && tr.docChanged) {
65
+ } else if (newState.preservedSelection && tr.docChanged) {
67
66
  newState.preservedSelection = (0, _selection.mapPreservedSelection)(newState.preservedSelection, tr);
68
67
  }
69
68
  if (!(0, _utils.compareSelections)(newState.preservedSelection, pluginState.preservedSelection)) {
@@ -97,19 +96,25 @@ var createSelectionPreservationPlugin = exports.createSelectionPreservationPlugi
97
96
  if (selectionUnchanged || selectionInvalid) {
98
97
  return null;
99
98
  }
100
- try {
101
- return newState.tr.setSelection(preservedSel);
102
- } catch (error) {
103
- (0, _monitoring.logException)(error, {
104
- location: 'editor-plugin-block-controls/SelectionPreservationPlugin'
99
+ var newSelection = (0, _selection.createPreservedSelection)(newState.doc.resolve(preservedSel.from), newState.doc.resolve(preservedSel.to));
100
+
101
+ // If selection becomes invalid, stop preserving
102
+ if (!newSelection) {
103
+ return (0, _editorCommands.stopPreservingSelection)({
104
+ tr: newState.tr
105
105
  });
106
106
  }
107
- return null;
107
+ return newState.tr.setSelection(newSelection);
108
108
  },
109
109
  view: function view() {
110
110
  return {
111
111
  update: function update(view, prevState) {
112
- if ((0, _utils.isPreservedSelectionChanged)(view.state, prevState)) {
112
+ var _selectionPreservatio, _selectionPreservatio2, _key$getState, _key$getState2;
113
+ var prevPreservedSelection = (_selectionPreservatio = _pluginKey.selectionPreservationPluginKey.getState(prevState)) === null || _selectionPreservatio === void 0 ? void 0 : _selectionPreservatio.preservedSelection;
114
+ var currPreservedSelection = (_selectionPreservatio2 = _pluginKey.selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio2 === void 0 ? void 0 : _selectionPreservatio2.preservedSelection;
115
+ var prevActiveNode = (_key$getState = _main.key.getState(prevState)) === null || _key$getState === void 0 ? void 0 : _key$getState.activeNode;
116
+ var currActiveNode = (_key$getState2 = _main.key.getState(view.state)) === null || _key$getState2 === void 0 ? void 0 : _key$getState2.activeNode;
117
+ if (currPreservedSelection && view.hasFocus() && (!(0, _utils.compareSelections)(prevPreservedSelection, currPreservedSelection) || prevActiveNode !== currActiveNode)) {
113
118
  (0, _utils.syncDOMSelection)(view.state.selection);
114
119
  }
115
120
  }
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.syncDOMSelection = exports.isSelectionWithinCodeBlock = exports.isPreservedSelectionChanged = exports.hasUserSelectionChange = exports.getSelectionPreservationMeta = exports.compareSelections = void 0;
6
+ exports.syncDOMSelection = exports.isSelectionWithinCodeBlock = exports.hasUserSelectionChange = exports.getSelectionPreservationMeta = exports.compareSelections = void 0;
7
7
  var _pluginKey = require("./plugin-key");
8
8
  /**
9
9
  * Detects if any of the transactions include user-driven selection changes.
@@ -40,22 +40,7 @@ var isSelectionWithinCodeBlock = exports.isSelectionWithinCodeBlock = function i
40
40
  * @returns True if both selections are equal, otherwise false.
41
41
  */
42
42
  var compareSelections = exports.compareSelections = function compareSelections(a, b) {
43
- return (a === null || a === void 0 ? void 0 : a.from) === (b === null || b === void 0 ? void 0 : b.from) && (a === null || a === void 0 ? void 0 : a.to) === (b === null || b === void 0 ? void 0 : b.to);
44
- };
45
-
46
- /**
47
- * Returns true/false indicating whether the preserved selection
48
- * has changed between the old and new editor states.
49
- *
50
- * @param newState The new editor state.
51
- * @param oldState The old editor state.
52
- * @returns True if the preserved selection has changed, otherwise false.
53
- */
54
- var isPreservedSelectionChanged = exports.isPreservedSelectionChanged = function isPreservedSelectionChanged(newState, oldState) {
55
- var _selectionPreservatio, _selectionPreservatio2;
56
- var prev = (_selectionPreservatio = _pluginKey.selectionPreservationPluginKey.getState(oldState)) === null || _selectionPreservatio === void 0 ? void 0 : _selectionPreservatio.preservedSelection;
57
- var curr = (_selectionPreservatio2 = _pluginKey.selectionPreservationPluginKey.getState(newState)) === null || _selectionPreservatio2 === void 0 ? void 0 : _selectionPreservatio2.preservedSelection;
58
- return !!prev && !!curr && !compareSelections(prev, curr);
43
+ return !a && !b || !!a && !!b && a.eq(b);
59
44
  };
60
45
 
61
46
  /**
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.setCursorPositionAtMovedNode = exports.selectNode = exports.rootTaskListDepth = exports.rootListDepth = exports.isNodeWithCodeBlock = exports.isHandleCorrelatedToSelection = exports.getSelection = exports.getInlineNodePos = void 0;
6
+ exports.setCursorPositionAtMovedNode = exports.selectNode = exports.rootTaskListDepth = exports.rootListDepth = exports.newGetSelection = exports.isNodeWithCodeBlock = exports.isHandleCorrelatedToSelection = exports.getSelection = exports.getInlineNodePos = void 0;
7
7
  var _selection2 = require("@atlaskit/editor-common/selection");
8
8
  var _toolbarFlagCheck = require("@atlaskit/editor-common/toolbar-flag-check");
9
9
  var _state = require("@atlaskit/editor-prosemirror/state");
@@ -81,7 +81,16 @@ var oldGetSelection = function oldGetSelection(tr, start) {
81
81
  return new _state.TextSelection(tr.doc.resolve(inlineNodePos), tr.doc.resolve(inlineNodeEndPos));
82
82
  }
83
83
  };
84
- var newGetSelection = function newGetSelection(doc, selectionEmpty, start) {
84
+
85
+ /**
86
+ * Gets the appropriate selection for the node at the given start position.
87
+ *
88
+ * @param doc The ProseMirror document.
89
+ * @param selectionEmpty Indicates if the current selection is empty.
90
+ * @param start The start position of the node.
91
+ * @returns The appropriate selection for the node.
92
+ */
93
+ var newGetSelection = exports.newGetSelection = function newGetSelection(doc, selectionEmpty, start) {
85
94
  var node = doc.nodeAt(start);
86
95
  var isNodeSelection = node && _state.NodeSelection.isSelectable(node);
87
96
  var nodeSize = node ? node.nodeSize : 1;
@@ -3,10 +3,12 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.mapPreservedSelection = exports.getSelectedSlicePosition = exports.getMultiSelectionIfPosInside = exports.expandSelectionHeadToNodeAtPos = exports.alignAnchorHeadInDirectionOfPos = void 0;
6
+ exports.mapPreservedSelection = exports.getSelectedSlicePosition = exports.getMultiSelectionIfPosInside = exports.expandSelectionHeadToNodeAtPos = exports.createPreservedSelection = exports.alignAnchorHeadInDirectionOfPos = void 0;
7
7
  var _selection = require("@atlaskit/editor-common/selection");
8
8
  var _state = require("@atlaskit/editor-prosemirror/state");
9
+ var _utils = require("@atlaskit/editor-tables/utils");
9
10
  var _main = require("../main");
11
+ var _getSelection = require("./getSelection");
10
12
  var getMultiSelectionIfPosInside = exports.getMultiSelectionIfPosInside = function getMultiSelectionIfPosInside(api, pos, tr) {
11
13
  var _api$blockControls, _pluginState$multiSel, _tr$getMeta;
12
14
  var pluginState = api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.sharedState.currentState();
@@ -91,38 +93,51 @@ var mapPreservedSelection = exports.mapPreservedSelection = function mapPreserve
91
93
  var _ref = (0, _main.getBlockControlsMeta)(tr) || {},
92
94
  preservedSelectionMapping = _ref.preservedSelectionMapping;
93
95
  var mapping = preservedSelectionMapping || tr.mapping;
94
- if (selection instanceof _state.TextSelection) {
95
- var from = mapping.map(selection.from);
96
- var to = mapping.map(selection.to);
97
- var isSelectionEmpty = from === to;
98
- var wasSelectionEmpty = selection.from === selection.to;
99
- if (isSelectionEmpty && !wasSelectionEmpty) {
100
- // If selection has become empty i.e. content has been deleted, stop preserving
101
- return undefined;
102
- }
103
- // expand the text selection range to block boundaries, so as document changes occur the
104
- // selection always includes whole nodes
105
- var expanded = (0, _selection.expandToBlockRange)(tr.doc.resolve(from), tr.doc.resolve(to));
96
+ var from = mapping.map(selection.from);
97
+ var to = mapping.map(selection.to);
98
+ var isSelectionEmpty = from === to;
99
+ var wasSelectionEmpty = selection.from === selection.to;
100
+ if (isSelectionEmpty && !wasSelectionEmpty) {
101
+ // If selection has become empty i.e. content has been deleted, stop preserving
102
+ return undefined;
103
+ }
104
+ return createPreservedSelection(tr.doc.resolve(from), tr.doc.resolve(to));
105
+ };
106
106
 
107
- // If after expanding the selection it is still empty, return a single cursor selection at 'from'
108
- var nodeAfter = expanded.$from.nodeAfter;
109
- var nodeBefore = expanded.$to.nodeBefore;
110
- var expandedSelectionEmpty = nodeAfter === nodeBefore && (nodeAfter === null || nodeAfter === void 0 ? void 0 : nodeAfter.content.size) === 0;
111
- if (isSelectionEmpty && expandedSelectionEmpty) {
112
- return _state.TextSelection.create(tr.doc, from);
113
- }
114
- var $from = expanded.$from,
115
- $to = expanded.$to;
107
+ /**
108
+ * Creates a preserved selection which is expanded to block boundaries.
109
+ *
110
+ * Will return the correct type of selection based on the nodes contained within the
111
+ * expanded selection range.
112
+ *
113
+ * If the selection becomes empty or invalid, it returns undefined.
114
+ *
115
+ * @param $from The resolved position of the start of the selection
116
+ * @param $to The resolved position of the end of the selection
117
+ * @returns A Selection or undefined if selection is invalid
118
+ */
119
+ var createPreservedSelection = exports.createPreservedSelection = function createPreservedSelection($from, $to) {
120
+ var _doc$nodeAt;
121
+ var doc = $from.doc;
122
+
123
+ // expand the selection range to block boundaries, so selection always includes whole nodes
124
+ var expanded = (0, _selection.expandToBlockRange)($from, $to);
125
+
126
+ // stop preserving if selection becomes invalid
127
+ if (expanded.$from.pos < 0 || expanded.$to.pos > doc.content.size || expanded.$from.pos >= expanded.$to.pos) {
128
+ return undefined;
129
+ }
116
130
 
117
- // stop preserving if preserved selection becomes invalid
118
- if ($from.pos < 0 || $to.pos > tr.doc.content.size || $from.pos >= $to.pos) {
119
- return undefined;
120
- }
121
- return new _state.TextSelection($from, $to);
131
+ // If multiple blocks selected, create TextSelection from start of first node to end of last node
132
+ if (expanded.range && (0, _selection.isMultiBlockRange)(expanded.range)) {
133
+ return new _state.TextSelection(expanded.$from, expanded.$to);
122
134
  }
123
- try {
124
- return selection.map(tr.doc, mapping);
125
- } catch (_unused) {
135
+ var nodeType = (_doc$nodeAt = doc.nodeAt(expanded.$from.pos)) === null || _doc$nodeAt === void 0 || (_doc$nodeAt = _doc$nodeAt.type) === null || _doc$nodeAt === void 0 ? void 0 : _doc$nodeAt.name;
136
+ if (!nodeType) {
126
137
  return undefined;
127
138
  }
139
+ if (nodeType === 'table') {
140
+ return (0, _utils.getTableSelectionClosesToPos)($from);
141
+ }
142
+ return (0, _getSelection.newGetSelection)(doc, false, expanded.$from.pos) || undefined;
128
143
  };
@@ -6,6 +6,7 @@ import { fg } from '@atlaskit/platform-feature-flags';
6
6
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
7
7
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
8
8
  import { handleKeyDownWithPreservedSelection } from './editor-commands/handle-key-down-with-preserved-selection';
9
+ import { mapPreservedSelection } from './editor-commands/map-preserved-selection';
9
10
  import { moveNode } from './editor-commands/move-node';
10
11
  import { moveNodeWithBlockMenu } from './editor-commands/move-node-with-block-menu';
11
12
  import { moveToLayout } from './editor-commands/move-to-layout';
@@ -238,6 +239,7 @@ export const blockControlsPlugin = ({
238
239
  isSelectedViaDragHandle
239
240
  });
240
241
  },
242
+ mapPreservedSelection: mapping => mapPreservedSelection(mapping),
241
243
  moveNodeWithBlockMenu: direction => moveNodeWithBlockMenu(api, direction),
242
244
  handleKeyDownWithPreservedSelection: handleKeyDownWithPreservedSelection(api),
243
245
  startPreservingSelection: () => startPreservingSelection,
@@ -0,0 +1,21 @@
1
+ import { key } from '../pm-plugins/main';
2
+
3
+ /**
4
+ * Applies metadata to the transaction which can be used to apply custom mapping
5
+ * to the preserved selection.
6
+ *
7
+ * This can be used when nodes are transformed/moved in a way that natural mapping
8
+ * would not correctly update the preserved selection.
9
+ *
10
+ * @param preservedSelectionMapping The mapping to apply to the preserved selection.
11
+ * @returns An editor command that sets the preserved selection mapping in the transaction metadata.
12
+ */
13
+ export const mapPreservedSelection = mapping => ({
14
+ tr
15
+ }) => {
16
+ const currMeta = tr.getMeta(key);
17
+ return tr.setMeta(key, {
18
+ ...currMeta,
19
+ preservedSelectionMapping: mapping
20
+ });
21
+ };
@@ -1,10 +1,10 @@
1
- import { logException } from '@atlaskit/editor-common/monitoring';
2
1
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
2
  import { DRAG_HANDLE_SELECTOR } from '@atlaskit/editor-common/styles';
4
- import { mapPreservedSelection } from '../utils/selection';
3
+ import { key } from '../main';
4
+ import { createPreservedSelection, mapPreservedSelection } from '../utils/selection';
5
5
  import { stopPreservingSelection } from './editor-commands';
6
6
  import { selectionPreservationPluginKey } from './plugin-key';
7
- import { compareSelections, getSelectionPreservationMeta, hasUserSelectionChange, isPreservedSelectionChanged, isSelectionWithinCodeBlock, syncDOMSelection } from './utils';
7
+ import { compareSelections, getSelectionPreservationMeta, hasUserSelectionChange, isSelectionWithinCodeBlock, syncDOMSelection } from './utils';
8
8
 
9
9
  /**
10
10
  * Selection Preservation Plugin
@@ -51,11 +51,10 @@ export const createSelectionPreservationPlugin = api => () => {
51
51
  ...pluginState
52
52
  };
53
53
  if ((meta === null || meta === void 0 ? void 0 : meta.type) === 'startPreserving') {
54
- newState.preservedSelection = mapPreservedSelection(tr.selection, tr);
54
+ newState.preservedSelection = createPreservedSelection(tr.doc.resolve(tr.selection.from), tr.doc.resolve(tr.selection.to));
55
55
  } else if ((meta === null || meta === void 0 ? void 0 : meta.type) === 'stopPreserving') {
56
56
  newState.preservedSelection = undefined;
57
- }
58
- if (newState.preservedSelection && tr.docChanged) {
57
+ } else if (newState.preservedSelection && tr.docChanged) {
59
58
  newState.preservedSelection = mapPreservedSelection(newState.preservedSelection, tr);
60
59
  }
61
60
  if (!compareSelections(newState.preservedSelection, pluginState.preservedSelection)) {
@@ -89,19 +88,25 @@ export const createSelectionPreservationPlugin = api => () => {
89
88
  if (selectionUnchanged || selectionInvalid) {
90
89
  return null;
91
90
  }
92
- try {
93
- return newState.tr.setSelection(preservedSel);
94
- } catch (error) {
95
- logException(error, {
96
- location: 'editor-plugin-block-controls/SelectionPreservationPlugin'
91
+ const newSelection = createPreservedSelection(newState.doc.resolve(preservedSel.from), newState.doc.resolve(preservedSel.to));
92
+
93
+ // If selection becomes invalid, stop preserving
94
+ if (!newSelection) {
95
+ return stopPreservingSelection({
96
+ tr: newState.tr
97
97
  });
98
98
  }
99
- return null;
99
+ return newState.tr.setSelection(newSelection);
100
100
  },
101
101
  view() {
102
102
  return {
103
103
  update(view, prevState) {
104
- if (isPreservedSelectionChanged(view.state, prevState)) {
104
+ var _selectionPreservatio, _selectionPreservatio2, _key$getState, _key$getState2;
105
+ const prevPreservedSelection = (_selectionPreservatio = selectionPreservationPluginKey.getState(prevState)) === null || _selectionPreservatio === void 0 ? void 0 : _selectionPreservatio.preservedSelection;
106
+ const currPreservedSelection = (_selectionPreservatio2 = selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio2 === void 0 ? void 0 : _selectionPreservatio2.preservedSelection;
107
+ const prevActiveNode = (_key$getState = key.getState(prevState)) === null || _key$getState === void 0 ? void 0 : _key$getState.activeNode;
108
+ const currActiveNode = (_key$getState2 = key.getState(view.state)) === null || _key$getState2 === void 0 ? void 0 : _key$getState2.activeNode;
109
+ if (currPreservedSelection && view.hasFocus() && (!compareSelections(prevPreservedSelection, currPreservedSelection) || prevActiveNode !== currActiveNode)) {
105
110
  syncDOMSelection(view.state.selection);
106
111
  }
107
112
  }
@@ -33,22 +33,7 @@ export const isSelectionWithinCodeBlock = ({
33
33
  * @returns True if both selections are equal, otherwise false.
34
34
  */
35
35
  export const compareSelections = (a, b) => {
36
- return (a === null || a === void 0 ? void 0 : a.from) === (b === null || b === void 0 ? void 0 : b.from) && (a === null || a === void 0 ? void 0 : a.to) === (b === null || b === void 0 ? void 0 : b.to);
37
- };
38
-
39
- /**
40
- * Returns true/false indicating whether the preserved selection
41
- * has changed between the old and new editor states.
42
- *
43
- * @param newState The new editor state.
44
- * @param oldState The old editor state.
45
- * @returns True if the preserved selection has changed, otherwise false.
46
- */
47
- export const isPreservedSelectionChanged = (newState, oldState) => {
48
- var _selectionPreservatio, _selectionPreservatio2;
49
- const prev = (_selectionPreservatio = selectionPreservationPluginKey.getState(oldState)) === null || _selectionPreservatio === void 0 ? void 0 : _selectionPreservatio.preservedSelection;
50
- const curr = (_selectionPreservatio2 = selectionPreservationPluginKey.getState(newState)) === null || _selectionPreservatio2 === void 0 ? void 0 : _selectionPreservatio2.preservedSelection;
51
- return !!prev && !!curr && !compareSelections(prev, curr);
36
+ return !a && !b || !!a && !!b && a.eq(b);
52
37
  };
53
38
 
54
39
  /**
@@ -76,7 +76,16 @@ const oldGetSelection = (tr, start) => {
76
76
  return new TextSelection(tr.doc.resolve(inlineNodePos), tr.doc.resolve(inlineNodeEndPos));
77
77
  }
78
78
  };
79
- const newGetSelection = (doc, selectionEmpty, start) => {
79
+
80
+ /**
81
+ * Gets the appropriate selection for the node at the given start position.
82
+ *
83
+ * @param doc The ProseMirror document.
84
+ * @param selectionEmpty Indicates if the current selection is empty.
85
+ * @param start The start position of the node.
86
+ * @returns The appropriate selection for the node.
87
+ */
88
+ export const newGetSelection = (doc, selectionEmpty, start) => {
80
89
  const node = doc.nodeAt(start);
81
90
  const isNodeSelection = node && NodeSelection.isSelectable(node);
82
91
  const nodeSize = node ? node.nodeSize : 1;
@@ -1,6 +1,8 @@
1
- import { expandToBlockRange } from '@atlaskit/editor-common/selection';
1
+ import { expandToBlockRange, isMultiBlockRange } from '@atlaskit/editor-common/selection';
2
2
  import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
3
+ import { getTableSelectionClosesToPos } from '@atlaskit/editor-tables/utils';
3
4
  import { getBlockControlsMeta, key } from '../main';
5
+ import { newGetSelection } from './getSelection';
4
6
  export const getMultiSelectionIfPosInside = (api, pos, tr) => {
5
7
  var _api$blockControls, _pluginState$multiSel, _tr$getMeta;
6
8
  const pluginState = api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.sharedState.currentState();
@@ -87,40 +89,53 @@ export const mapPreservedSelection = (selection, tr) => {
87
89
  preservedSelectionMapping
88
90
  } = getBlockControlsMeta(tr) || {};
89
91
  const mapping = preservedSelectionMapping || tr.mapping;
90
- if (selection instanceof TextSelection) {
91
- const from = mapping.map(selection.from);
92
- const to = mapping.map(selection.to);
93
- const isSelectionEmpty = from === to;
94
- const wasSelectionEmpty = selection.from === selection.to;
95
- if (isSelectionEmpty && !wasSelectionEmpty) {
96
- // If selection has become empty i.e. content has been deleted, stop preserving
97
- return undefined;
98
- }
99
- // expand the text selection range to block boundaries, so as document changes occur the
100
- // selection always includes whole nodes
101
- const expanded = expandToBlockRange(tr.doc.resolve(from), tr.doc.resolve(to));
92
+ const from = mapping.map(selection.from);
93
+ const to = mapping.map(selection.to);
94
+ const isSelectionEmpty = from === to;
95
+ const wasSelectionEmpty = selection.from === selection.to;
96
+ if (isSelectionEmpty && !wasSelectionEmpty) {
97
+ // If selection has become empty i.e. content has been deleted, stop preserving
98
+ return undefined;
99
+ }
100
+ return createPreservedSelection(tr.doc.resolve(from), tr.doc.resolve(to));
101
+ };
102
+
103
+ /**
104
+ * Creates a preserved selection which is expanded to block boundaries.
105
+ *
106
+ * Will return the correct type of selection based on the nodes contained within the
107
+ * expanded selection range.
108
+ *
109
+ * If the selection becomes empty or invalid, it returns undefined.
110
+ *
111
+ * @param $from The resolved position of the start of the selection
112
+ * @param $to The resolved position of the end of the selection
113
+ * @returns A Selection or undefined if selection is invalid
114
+ */
115
+ export const createPreservedSelection = ($from, $to) => {
116
+ var _doc$nodeAt, _doc$nodeAt$type;
117
+ const {
118
+ doc
119
+ } = $from;
102
120
 
103
- // If after expanding the selection it is still empty, return a single cursor selection at 'from'
104
- const nodeAfter = expanded.$from.nodeAfter;
105
- const nodeBefore = expanded.$to.nodeBefore;
106
- const expandedSelectionEmpty = nodeAfter === nodeBefore && (nodeAfter === null || nodeAfter === void 0 ? void 0 : nodeAfter.content.size) === 0;
107
- if (isSelectionEmpty && expandedSelectionEmpty) {
108
- return TextSelection.create(tr.doc, from);
109
- }
110
- const {
111
- $from,
112
- $to
113
- } = expanded;
121
+ // expand the selection range to block boundaries, so selection always includes whole nodes
122
+ const expanded = expandToBlockRange($from, $to);
114
123
 
115
- // stop preserving if preserved selection becomes invalid
116
- if ($from.pos < 0 || $to.pos > tr.doc.content.size || $from.pos >= $to.pos) {
117
- return undefined;
118
- }
119
- return new TextSelection($from, $to);
124
+ // stop preserving if selection becomes invalid
125
+ if (expanded.$from.pos < 0 || expanded.$to.pos > doc.content.size || expanded.$from.pos >= expanded.$to.pos) {
126
+ return undefined;
120
127
  }
121
- try {
122
- return selection.map(tr.doc, mapping);
123
- } catch {
128
+
129
+ // If multiple blocks selected, create TextSelection from start of first node to end of last node
130
+ if (expanded.range && isMultiBlockRange(expanded.range)) {
131
+ return new TextSelection(expanded.$from, expanded.$to);
132
+ }
133
+ const nodeType = (_doc$nodeAt = doc.nodeAt(expanded.$from.pos)) === null || _doc$nodeAt === void 0 ? void 0 : (_doc$nodeAt$type = _doc$nodeAt.type) === null || _doc$nodeAt$type === void 0 ? void 0 : _doc$nodeAt$type.name;
134
+ if (!nodeType) {
124
135
  return undefined;
125
136
  }
137
+ if (nodeType === 'table') {
138
+ return getTableSelectionClosesToPos($from);
139
+ }
140
+ return newGetSelection(doc, false, expanded.$from.pos) || undefined;
126
141
  };
@@ -9,6 +9,7 @@ import { fg } from '@atlaskit/platform-feature-flags';
9
9
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
10
10
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
11
11
  import { handleKeyDownWithPreservedSelection } from './editor-commands/handle-key-down-with-preserved-selection';
12
+ import { mapPreservedSelection as _mapPreservedSelection } from './editor-commands/map-preserved-selection';
12
13
  import { moveNode } from './editor-commands/move-node';
13
14
  import { moveNodeWithBlockMenu as _moveNodeWithBlockMenu } from './editor-commands/move-node-with-block-menu';
14
15
  import { moveToLayout } from './editor-commands/move-to-layout';
@@ -237,6 +238,9 @@ export var blockControlsPlugin = function blockControlsPlugin(_ref) {
237
238
  }));
238
239
  };
239
240
  },
241
+ mapPreservedSelection: function mapPreservedSelection(mapping) {
242
+ return _mapPreservedSelection(mapping);
243
+ },
240
244
  moveNodeWithBlockMenu: function moveNodeWithBlockMenu(direction) {
241
245
  return _moveNodeWithBlockMenu(api, direction);
242
246
  },
@@ -0,0 +1,24 @@
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; }
4
+ import { key } from '../pm-plugins/main';
5
+
6
+ /**
7
+ * Applies metadata to the transaction which can be used to apply custom mapping
8
+ * to the preserved selection.
9
+ *
10
+ * This can be used when nodes are transformed/moved in a way that natural mapping
11
+ * would not correctly update the preserved selection.
12
+ *
13
+ * @param preservedSelectionMapping The mapping to apply to the preserved selection.
14
+ * @returns An editor command that sets the preserved selection mapping in the transaction metadata.
15
+ */
16
+ export var mapPreservedSelection = function mapPreservedSelection(mapping) {
17
+ return function (_ref) {
18
+ var tr = _ref.tr;
19
+ var currMeta = tr.getMeta(key);
20
+ return tr.setMeta(key, _objectSpread(_objectSpread({}, currMeta), {}, {
21
+ preservedSelectionMapping: mapping
22
+ }));
23
+ };
24
+ };
@@ -1,13 +1,13 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
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
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; }
4
- import { logException } from '@atlaskit/editor-common/monitoring';
5
4
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
6
5
  import { DRAG_HANDLE_SELECTOR } from '@atlaskit/editor-common/styles';
7
- import { mapPreservedSelection } from '../utils/selection';
6
+ import { key } from '../main';
7
+ import { createPreservedSelection, mapPreservedSelection } from '../utils/selection';
8
8
  import { stopPreservingSelection } from './editor-commands';
9
9
  import { selectionPreservationPluginKey } from './plugin-key';
10
- import { compareSelections, getSelectionPreservationMeta, hasUserSelectionChange, isPreservedSelectionChanged, isSelectionWithinCodeBlock, syncDOMSelection } from './utils';
10
+ import { compareSelections, getSelectionPreservationMeta, hasUserSelectionChange, isSelectionWithinCodeBlock, syncDOMSelection } from './utils';
11
11
 
12
12
  /**
13
13
  * Selection Preservation Plugin
@@ -53,11 +53,10 @@ export var createSelectionPreservationPlugin = function createSelectionPreservat
53
53
  var meta = getSelectionPreservationMeta(tr);
54
54
  var newState = _objectSpread({}, pluginState);
55
55
  if ((meta === null || meta === void 0 ? void 0 : meta.type) === 'startPreserving') {
56
- newState.preservedSelection = mapPreservedSelection(tr.selection, tr);
56
+ newState.preservedSelection = createPreservedSelection(tr.doc.resolve(tr.selection.from), tr.doc.resolve(tr.selection.to));
57
57
  } else if ((meta === null || meta === void 0 ? void 0 : meta.type) === 'stopPreserving') {
58
58
  newState.preservedSelection = undefined;
59
- }
60
- if (newState.preservedSelection && tr.docChanged) {
59
+ } else if (newState.preservedSelection && tr.docChanged) {
61
60
  newState.preservedSelection = mapPreservedSelection(newState.preservedSelection, tr);
62
61
  }
63
62
  if (!compareSelections(newState.preservedSelection, pluginState.preservedSelection)) {
@@ -91,19 +90,25 @@ export var createSelectionPreservationPlugin = function createSelectionPreservat
91
90
  if (selectionUnchanged || selectionInvalid) {
92
91
  return null;
93
92
  }
94
- try {
95
- return newState.tr.setSelection(preservedSel);
96
- } catch (error) {
97
- logException(error, {
98
- location: 'editor-plugin-block-controls/SelectionPreservationPlugin'
93
+ var newSelection = createPreservedSelection(newState.doc.resolve(preservedSel.from), newState.doc.resolve(preservedSel.to));
94
+
95
+ // If selection becomes invalid, stop preserving
96
+ if (!newSelection) {
97
+ return stopPreservingSelection({
98
+ tr: newState.tr
99
99
  });
100
100
  }
101
- return null;
101
+ return newState.tr.setSelection(newSelection);
102
102
  },
103
103
  view: function view() {
104
104
  return {
105
105
  update: function update(view, prevState) {
106
- if (isPreservedSelectionChanged(view.state, prevState)) {
106
+ var _selectionPreservatio, _selectionPreservatio2, _key$getState, _key$getState2;
107
+ var prevPreservedSelection = (_selectionPreservatio = selectionPreservationPluginKey.getState(prevState)) === null || _selectionPreservatio === void 0 ? void 0 : _selectionPreservatio.preservedSelection;
108
+ var currPreservedSelection = (_selectionPreservatio2 = selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio2 === void 0 ? void 0 : _selectionPreservatio2.preservedSelection;
109
+ var prevActiveNode = (_key$getState = key.getState(prevState)) === null || _key$getState === void 0 ? void 0 : _key$getState.activeNode;
110
+ var currActiveNode = (_key$getState2 = key.getState(view.state)) === null || _key$getState2 === void 0 ? void 0 : _key$getState2.activeNode;
111
+ if (currPreservedSelection && view.hasFocus() && (!compareSelections(prevPreservedSelection, currPreservedSelection) || prevActiveNode !== currActiveNode)) {
107
112
  syncDOMSelection(view.state.selection);
108
113
  }
109
114
  }
@@ -34,22 +34,7 @@ export var isSelectionWithinCodeBlock = function isSelectionWithinCodeBlock(_ref
34
34
  * @returns True if both selections are equal, otherwise false.
35
35
  */
36
36
  export var compareSelections = function compareSelections(a, b) {
37
- return (a === null || a === void 0 ? void 0 : a.from) === (b === null || b === void 0 ? void 0 : b.from) && (a === null || a === void 0 ? void 0 : a.to) === (b === null || b === void 0 ? void 0 : b.to);
38
- };
39
-
40
- /**
41
- * Returns true/false indicating whether the preserved selection
42
- * has changed between the old and new editor states.
43
- *
44
- * @param newState The new editor state.
45
- * @param oldState The old editor state.
46
- * @returns True if the preserved selection has changed, otherwise false.
47
- */
48
- export var isPreservedSelectionChanged = function isPreservedSelectionChanged(newState, oldState) {
49
- var _selectionPreservatio, _selectionPreservatio2;
50
- var prev = (_selectionPreservatio = selectionPreservationPluginKey.getState(oldState)) === null || _selectionPreservatio === void 0 ? void 0 : _selectionPreservatio.preservedSelection;
51
- var curr = (_selectionPreservatio2 = selectionPreservationPluginKey.getState(newState)) === null || _selectionPreservatio2 === void 0 ? void 0 : _selectionPreservatio2.preservedSelection;
52
- return !!prev && !!curr && !compareSelections(prev, curr);
37
+ return !a && !b || !!a && !!b && a.eq(b);
53
38
  };
54
39
 
55
40
  /**
@@ -75,7 +75,16 @@ var oldGetSelection = function oldGetSelection(tr, start) {
75
75
  return new TextSelection(tr.doc.resolve(inlineNodePos), tr.doc.resolve(inlineNodeEndPos));
76
76
  }
77
77
  };
78
- var newGetSelection = function newGetSelection(doc, selectionEmpty, start) {
78
+
79
+ /**
80
+ * Gets the appropriate selection for the node at the given start position.
81
+ *
82
+ * @param doc The ProseMirror document.
83
+ * @param selectionEmpty Indicates if the current selection is empty.
84
+ * @param start The start position of the node.
85
+ * @returns The appropriate selection for the node.
86
+ */
87
+ export var newGetSelection = function newGetSelection(doc, selectionEmpty, start) {
79
88
  var node = doc.nodeAt(start);
80
89
  var isNodeSelection = node && NodeSelection.isSelectable(node);
81
90
  var nodeSize = node ? node.nodeSize : 1;
@@ -1,6 +1,8 @@
1
- import { expandToBlockRange } from '@atlaskit/editor-common/selection';
1
+ import { expandToBlockRange, isMultiBlockRange } from '@atlaskit/editor-common/selection';
2
2
  import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
3
+ import { getTableSelectionClosesToPos } from '@atlaskit/editor-tables/utils';
3
4
  import { getBlockControlsMeta, key } from '../main';
5
+ import { newGetSelection } from './getSelection';
4
6
  export var getMultiSelectionIfPosInside = function getMultiSelectionIfPosInside(api, pos, tr) {
5
7
  var _api$blockControls, _pluginState$multiSel, _tr$getMeta;
6
8
  var pluginState = api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.sharedState.currentState();
@@ -85,38 +87,51 @@ export var mapPreservedSelection = function mapPreservedSelection(selection, tr)
85
87
  var _ref = getBlockControlsMeta(tr) || {},
86
88
  preservedSelectionMapping = _ref.preservedSelectionMapping;
87
89
  var mapping = preservedSelectionMapping || tr.mapping;
88
- if (selection instanceof TextSelection) {
89
- var from = mapping.map(selection.from);
90
- var to = mapping.map(selection.to);
91
- var isSelectionEmpty = from === to;
92
- var wasSelectionEmpty = selection.from === selection.to;
93
- if (isSelectionEmpty && !wasSelectionEmpty) {
94
- // If selection has become empty i.e. content has been deleted, stop preserving
95
- return undefined;
96
- }
97
- // expand the text selection range to block boundaries, so as document changes occur the
98
- // selection always includes whole nodes
99
- var expanded = expandToBlockRange(tr.doc.resolve(from), tr.doc.resolve(to));
90
+ var from = mapping.map(selection.from);
91
+ var to = mapping.map(selection.to);
92
+ var isSelectionEmpty = from === to;
93
+ var wasSelectionEmpty = selection.from === selection.to;
94
+ if (isSelectionEmpty && !wasSelectionEmpty) {
95
+ // If selection has become empty i.e. content has been deleted, stop preserving
96
+ return undefined;
97
+ }
98
+ return createPreservedSelection(tr.doc.resolve(from), tr.doc.resolve(to));
99
+ };
100
100
 
101
- // If after expanding the selection it is still empty, return a single cursor selection at 'from'
102
- var nodeAfter = expanded.$from.nodeAfter;
103
- var nodeBefore = expanded.$to.nodeBefore;
104
- var expandedSelectionEmpty = nodeAfter === nodeBefore && (nodeAfter === null || nodeAfter === void 0 ? void 0 : nodeAfter.content.size) === 0;
105
- if (isSelectionEmpty && expandedSelectionEmpty) {
106
- return TextSelection.create(tr.doc, from);
107
- }
108
- var $from = expanded.$from,
109
- $to = expanded.$to;
101
+ /**
102
+ * Creates a preserved selection which is expanded to block boundaries.
103
+ *
104
+ * Will return the correct type of selection based on the nodes contained within the
105
+ * expanded selection range.
106
+ *
107
+ * If the selection becomes empty or invalid, it returns undefined.
108
+ *
109
+ * @param $from The resolved position of the start of the selection
110
+ * @param $to The resolved position of the end of the selection
111
+ * @returns A Selection or undefined if selection is invalid
112
+ */
113
+ export var createPreservedSelection = function createPreservedSelection($from, $to) {
114
+ var _doc$nodeAt;
115
+ var doc = $from.doc;
116
+
117
+ // expand the selection range to block boundaries, so selection always includes whole nodes
118
+ var expanded = expandToBlockRange($from, $to);
119
+
120
+ // stop preserving if selection becomes invalid
121
+ if (expanded.$from.pos < 0 || expanded.$to.pos > doc.content.size || expanded.$from.pos >= expanded.$to.pos) {
122
+ return undefined;
123
+ }
110
124
 
111
- // stop preserving if preserved selection becomes invalid
112
- if ($from.pos < 0 || $to.pos > tr.doc.content.size || $from.pos >= $to.pos) {
113
- return undefined;
114
- }
115
- return new TextSelection($from, $to);
125
+ // If multiple blocks selected, create TextSelection from start of first node to end of last node
126
+ if (expanded.range && isMultiBlockRange(expanded.range)) {
127
+ return new TextSelection(expanded.$from, expanded.$to);
116
128
  }
117
- try {
118
- return selection.map(tr.doc, mapping);
119
- } catch (_unused) {
129
+ var nodeType = (_doc$nodeAt = doc.nodeAt(expanded.$from.pos)) === null || _doc$nodeAt === void 0 || (_doc$nodeAt = _doc$nodeAt.type) === null || _doc$nodeAt === void 0 ? void 0 : _doc$nodeAt.name;
130
+ if (!nodeType) {
120
131
  return undefined;
121
132
  }
133
+ if (nodeType === 'table') {
134
+ return getTableSelectionClosesToPos($from);
135
+ }
136
+ return newGetSelection(doc, false, expanded.$from.pos) || undefined;
122
137
  };
@@ -119,6 +119,7 @@ export type BlockControlsPluginDependencies = [
119
119
  export type BlockControlsPlugin = NextEditorPlugin<'blockControls', {
120
120
  commands: {
121
121
  handleKeyDownWithPreservedSelection: (event: KeyboardEvent) => EditorCommand;
122
+ mapPreservedSelection: (mapping: Mapping) => EditorCommand;
122
123
  moveNode: MoveNode;
123
124
  moveNodeWithBlockMenu: (direction: DIRECTION.UP | DIRECTION.DOWN) => EditorCommand;
124
125
  /**
@@ -0,0 +1,13 @@
1
+ import type { EditorCommand } from '@atlaskit/editor-common/types';
2
+ import type { Mapping } from '@atlaskit/editor-prosemirror/transform';
3
+ /**
4
+ * Applies metadata to the transaction which can be used to apply custom mapping
5
+ * to the preserved selection.
6
+ *
7
+ * This can be used when nodes are transformed/moved in a way that natural mapping
8
+ * would not correctly update the preserved selection.
9
+ *
10
+ * @param preservedSelectionMapping The mapping to apply to the preserved selection.
11
+ * @returns An editor command that sets the preserved selection mapping in the transaction metadata.
12
+ */
13
+ export declare const mapPreservedSelection: (mapping: Mapping) => EditorCommand;
@@ -1,4 +1,4 @@
1
- import type { EditorState, ReadonlyTransaction, Selection, Transaction } from '@atlaskit/editor-prosemirror/state';
1
+ import type { ReadonlyTransaction, Selection, Transaction } from '@atlaskit/editor-prosemirror/state';
2
2
  import type { SelectionPreservationMeta } from './types';
3
3
  /**
4
4
  * Detects if any of the transactions include user-driven selection changes.
@@ -23,15 +23,6 @@ export declare const isSelectionWithinCodeBlock: ({ $from, $to }: Selection) =>
23
23
  * @returns True if both selections are equal, otherwise false.
24
24
  */
25
25
  export declare const compareSelections: (a?: Selection, b?: Selection) => boolean;
26
- /**
27
- * Returns true/false indicating whether the preserved selection
28
- * has changed between the old and new editor states.
29
- *
30
- * @param newState The new editor state.
31
- * @param oldState The old editor state.
32
- * @returns True if the preserved selection has changed, otherwise false.
33
- */
34
- export declare const isPreservedSelectionChanged: (newState: EditorState, oldState: EditorState) => boolean;
35
26
  /**
36
27
  * Triggers a DOM selection sync by resetting the current native selection range
37
28
  * only if it is out of sync with the provided ProseMirror selection state.
@@ -7,6 +7,15 @@ export declare const getInlineNodePos: (doc: PMNode, start: number, nodeSize: nu
7
7
  inlineNodePos: number;
8
8
  };
9
9
  export declare const isNodeWithCodeBlock: (tr: Transaction, start: number, nodeSize: number) => boolean;
10
+ /**
11
+ * Gets the appropriate selection for the node at the given start position.
12
+ *
13
+ * @param doc The ProseMirror document.
14
+ * @param selectionEmpty Indicates if the current selection is empty.
15
+ * @param start The start position of the node.
16
+ * @returns The appropriate selection for the node.
17
+ */
18
+ export declare const newGetSelection: (doc: PMNode, selectionEmpty: boolean, start: number) => false | NodeSelection | TextSelection;
10
19
  export declare const getSelection: (tr: Transaction, start: number, api?: ExtractInjectionAPI<BlockControlsPlugin>) => false | NodeSelection | TextSelection;
11
20
  export declare const selectNode: (tr: Transaction, start: number, nodeType: string, api?: ExtractInjectionAPI<BlockControlsPlugin>) => Transaction;
12
21
  export declare const setCursorPositionAtMovedNode: (tr: Transaction, start: number, api?: ExtractInjectionAPI<BlockControlsPlugin>) => Transaction;
@@ -1,5 +1,6 @@
1
1
  import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
2
- import { type ReadonlyTransaction, type Selection, type Transaction } from '@atlaskit/editor-prosemirror/state';
2
+ import type { ResolvedPos } from '@atlaskit/editor-prosemirror/model';
3
+ import { NodeSelection, TextSelection, type ReadonlyTransaction, type Selection, type Transaction } from '@atlaskit/editor-prosemirror/state';
3
4
  import type { BlockControlsPlugin } from '../../blockControlsPluginType';
4
5
  export declare const getMultiSelectionIfPosInside: (api: ExtractInjectionAPI<BlockControlsPlugin>, pos: number, tr?: Transaction) => {
5
6
  anchor?: number;
@@ -38,3 +39,16 @@ export declare const alignAnchorHeadInDirectionOfPos: (selection: Selection, pos
38
39
  * @returns The mapped selection or undefined if mapping is not possible
39
40
  */
40
41
  export declare const mapPreservedSelection: (selection: Selection, tr: ReadonlyTransaction | Transaction) => Selection | undefined;
42
+ /**
43
+ * Creates a preserved selection which is expanded to block boundaries.
44
+ *
45
+ * Will return the correct type of selection based on the nodes contained within the
46
+ * expanded selection range.
47
+ *
48
+ * If the selection becomes empty or invalid, it returns undefined.
49
+ *
50
+ * @param $from The resolved position of the start of the selection
51
+ * @param $to The resolved position of the end of the selection
52
+ * @returns A Selection or undefined if selection is invalid
53
+ */
54
+ export declare const createPreservedSelection: ($from: ResolvedPos, $to: ResolvedPos) => NodeSelection | TextSelection | import("@atlaskit/editor-tables").CellSelection | undefined;
@@ -119,6 +119,7 @@ export type BlockControlsPluginDependencies = [
119
119
  export type BlockControlsPlugin = NextEditorPlugin<'blockControls', {
120
120
  commands: {
121
121
  handleKeyDownWithPreservedSelection: (event: KeyboardEvent) => EditorCommand;
122
+ mapPreservedSelection: (mapping: Mapping) => EditorCommand;
122
123
  moveNode: MoveNode;
123
124
  moveNodeWithBlockMenu: (direction: DIRECTION.UP | DIRECTION.DOWN) => EditorCommand;
124
125
  /**
@@ -0,0 +1,13 @@
1
+ import type { EditorCommand } from '@atlaskit/editor-common/types';
2
+ import type { Mapping } from '@atlaskit/editor-prosemirror/transform';
3
+ /**
4
+ * Applies metadata to the transaction which can be used to apply custom mapping
5
+ * to the preserved selection.
6
+ *
7
+ * This can be used when nodes are transformed/moved in a way that natural mapping
8
+ * would not correctly update the preserved selection.
9
+ *
10
+ * @param preservedSelectionMapping The mapping to apply to the preserved selection.
11
+ * @returns An editor command that sets the preserved selection mapping in the transaction metadata.
12
+ */
13
+ export declare const mapPreservedSelection: (mapping: Mapping) => EditorCommand;
@@ -1,4 +1,4 @@
1
- import type { EditorState, ReadonlyTransaction, Selection, Transaction } from '@atlaskit/editor-prosemirror/state';
1
+ import type { ReadonlyTransaction, Selection, Transaction } from '@atlaskit/editor-prosemirror/state';
2
2
  import type { SelectionPreservationMeta } from './types';
3
3
  /**
4
4
  * Detects if any of the transactions include user-driven selection changes.
@@ -23,15 +23,6 @@ export declare const isSelectionWithinCodeBlock: ({ $from, $to }: Selection) =>
23
23
  * @returns True if both selections are equal, otherwise false.
24
24
  */
25
25
  export declare const compareSelections: (a?: Selection, b?: Selection) => boolean;
26
- /**
27
- * Returns true/false indicating whether the preserved selection
28
- * has changed between the old and new editor states.
29
- *
30
- * @param newState The new editor state.
31
- * @param oldState The old editor state.
32
- * @returns True if the preserved selection has changed, otherwise false.
33
- */
34
- export declare const isPreservedSelectionChanged: (newState: EditorState, oldState: EditorState) => boolean;
35
26
  /**
36
27
  * Triggers a DOM selection sync by resetting the current native selection range
37
28
  * only if it is out of sync with the provided ProseMirror selection state.
@@ -7,6 +7,15 @@ export declare const getInlineNodePos: (doc: PMNode, start: number, nodeSize: nu
7
7
  inlineNodePos: number;
8
8
  };
9
9
  export declare const isNodeWithCodeBlock: (tr: Transaction, start: number, nodeSize: number) => boolean;
10
+ /**
11
+ * Gets the appropriate selection for the node at the given start position.
12
+ *
13
+ * @param doc The ProseMirror document.
14
+ * @param selectionEmpty Indicates if the current selection is empty.
15
+ * @param start The start position of the node.
16
+ * @returns The appropriate selection for the node.
17
+ */
18
+ export declare const newGetSelection: (doc: PMNode, selectionEmpty: boolean, start: number) => false | NodeSelection | TextSelection;
10
19
  export declare const getSelection: (tr: Transaction, start: number, api?: ExtractInjectionAPI<BlockControlsPlugin>) => false | NodeSelection | TextSelection;
11
20
  export declare const selectNode: (tr: Transaction, start: number, nodeType: string, api?: ExtractInjectionAPI<BlockControlsPlugin>) => Transaction;
12
21
  export declare const setCursorPositionAtMovedNode: (tr: Transaction, start: number, api?: ExtractInjectionAPI<BlockControlsPlugin>) => Transaction;
@@ -1,5 +1,6 @@
1
1
  import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
2
- import { type ReadonlyTransaction, type Selection, type Transaction } from '@atlaskit/editor-prosemirror/state';
2
+ import type { ResolvedPos } from '@atlaskit/editor-prosemirror/model';
3
+ import { NodeSelection, TextSelection, type ReadonlyTransaction, type Selection, type Transaction } from '@atlaskit/editor-prosemirror/state';
3
4
  import type { BlockControlsPlugin } from '../../blockControlsPluginType';
4
5
  export declare const getMultiSelectionIfPosInside: (api: ExtractInjectionAPI<BlockControlsPlugin>, pos: number, tr?: Transaction) => {
5
6
  anchor?: number;
@@ -38,3 +39,16 @@ export declare const alignAnchorHeadInDirectionOfPos: (selection: Selection, pos
38
39
  * @returns The mapped selection or undefined if mapping is not possible
39
40
  */
40
41
  export declare const mapPreservedSelection: (selection: Selection, tr: ReadonlyTransaction | Transaction) => Selection | undefined;
42
+ /**
43
+ * Creates a preserved selection which is expanded to block boundaries.
44
+ *
45
+ * Will return the correct type of selection based on the nodes contained within the
46
+ * expanded selection range.
47
+ *
48
+ * If the selection becomes empty or invalid, it returns undefined.
49
+ *
50
+ * @param $from The resolved position of the start of the selection
51
+ * @param $to The resolved position of the end of the selection
52
+ * @returns A Selection or undefined if selection is invalid
53
+ */
54
+ export declare const createPreservedSelection: ($from: ResolvedPos, $to: ResolvedPos) => NodeSelection | TextSelection | import("@atlaskit/editor-tables").CellSelection | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-block-controls",
3
- "version": "8.0.7",
3
+ "version": "8.0.9",
4
4
  "description": "Block controls plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -54,9 +54,9 @@
54
54
  "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^3.2.0",
55
55
  "@atlaskit/primitives": "^17.0.0",
56
56
  "@atlaskit/theme": "^21.0.0",
57
- "@atlaskit/tmp-editor-statsig": "^16.13.0",
57
+ "@atlaskit/tmp-editor-statsig": "^16.15.0",
58
58
  "@atlaskit/tokens": "^9.1.0",
59
- "@atlaskit/tooltip": "^20.13.0",
59
+ "@atlaskit/tooltip": "^20.14.0",
60
60
  "@babel/runtime": "^7.0.0",
61
61
  "@emotion/react": "^11.7.1",
62
62
  "@popperjs/core": "^2.11.8",