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

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 (27) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cjs/editor-commands/move-node-with-block-menu.js +6 -6
  3. package/dist/cjs/editor-commands/move-node.js +0 -3
  4. package/dist/cjs/editor-commands/utils/move-node-utils.js +57 -45
  5. package/dist/cjs/pm-plugins/handle-mouse-over.js +5 -0
  6. package/dist/cjs/pm-plugins/selection-preservation/pm-plugin.js +50 -28
  7. package/dist/cjs/pm-plugins/selection-preservation/utils.js +1 -13
  8. package/dist/cjs/pm-plugins/utils/getSelection.js +10 -0
  9. package/dist/es2019/editor-commands/move-node-with-block-menu.js +7 -7
  10. package/dist/es2019/editor-commands/move-node.js +0 -3
  11. package/dist/es2019/editor-commands/utils/move-node-utils.js +49 -37
  12. package/dist/es2019/pm-plugins/handle-mouse-over.js +5 -0
  13. package/dist/es2019/pm-plugins/selection-preservation/pm-plugin.js +52 -30
  14. package/dist/es2019/pm-plugins/selection-preservation/utils.js +0 -13
  15. package/dist/es2019/pm-plugins/utils/getSelection.js +8 -0
  16. package/dist/esm/editor-commands/move-node-with-block-menu.js +7 -7
  17. package/dist/esm/editor-commands/move-node.js +0 -3
  18. package/dist/esm/editor-commands/utils/move-node-utils.js +57 -44
  19. package/dist/esm/pm-plugins/handle-mouse-over.js +5 -0
  20. package/dist/esm/pm-plugins/selection-preservation/pm-plugin.js +51 -29
  21. package/dist/esm/pm-plugins/selection-preservation/utils.js +0 -12
  22. package/dist/esm/pm-plugins/utils/getSelection.js +10 -0
  23. package/dist/types/editor-commands/utils/move-node-utils.d.ts +16 -8
  24. package/dist/types/pm-plugins/selection-preservation/utils.d.ts +0 -7
  25. package/dist/types-ts4.5/editor-commands/utils/move-node-utils.d.ts +16 -8
  26. package/dist/types-ts4.5/pm-plugins/selection-preservation/utils.d.ts +0 -7
  27. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @atlaskit/editor-plugin-block-controls
2
2
 
3
+ ## 8.0.11
4
+
5
+ ### Patch Changes
6
+
7
+ - [`de045021d126d`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/de045021d126d) -
8
+ EDITOR-4435 fix code block selection focus
9
+ - [`76afb688dff6a`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/76afb688dff6a) -
10
+ [ux] Update move function to leverage preserveSelection logic for different types of selection
11
+ - Updated dependencies
12
+
13
+ ## 8.0.10
14
+
15
+ ### Patch Changes
16
+
17
+ - [`df9b89b4945d1`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/df9b89b4945d1) -
18
+ Editor-4255: "Fix heading with alignment nested inside layout not showing block menu"
19
+ - Updated dependencies
20
+
3
21
  ## 8.0.9
4
22
 
5
23
  ### Patch Changes
@@ -36,12 +36,12 @@ var moveNodeWithBlockMenu = exports.moveNodeWithBlockMenu = function moveNodeWit
36
36
  var preservedSelection = api === null || api === void 0 || (_api$blockControls$sh = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh === void 0 ? void 0 : _api$blockControls$sh.preservedSelection;
37
37
  var selection = preservedSelection !== null && preservedSelection !== void 0 ? preservedSelection : tr.selection;
38
38
 
39
- // Nodes like lists nest within themselves, we need to find the top most position
40
- var currentNodePos = (0, _moveNodeUtils.getCurrentNodePosFromDragHandleSelection)({
41
- selection: selection,
42
- schema: tr.doc.type.schema,
43
- resolve: tr.doc.resolve.bind(tr.doc)
44
- });
39
+ // Use getNodeBoundsFromSelection to get the proper block boundaries
40
+ var nodeBounds = (0, _moveNodeUtils.getNodeBoundsFromSelection)(selection);
41
+ if (!nodeBounds) {
42
+ return tr;
43
+ }
44
+ var currentNodePos = nodeBounds.from;
45
45
  var $from = selection.$from,
46
46
  $to = selection.$to;
47
47
  var depth = tr.doc.resolve(currentNodePos).depth;
@@ -394,9 +394,6 @@ var moveNode = exports.moveNode = function moveNode(api) {
394
394
  if (!convertedNode) {
395
395
  return tr;
396
396
  }
397
- if ((sourceNode === null || sourceNode === void 0 ? void 0 : sourceNode.type.name) === 'taskList' && sliceFrom > 0 && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true)) {
398
- sliceFrom = sliceFrom - 1;
399
- }
400
397
 
401
398
  // Currently we don't support breakout mark for children nodes of bodiedSyncBlock node
402
399
  // Hence strip out the mark for now
@@ -3,42 +3,57 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.getShouldMoveNode = exports.getPosWhenMoveNodeUp = exports.getPosWhenMoveNodeDown = exports.getCurrentNodePosFromDragHandleSelection = exports.canMoveNodeUpOrDown = void 0;
7
- var _selection = require("@atlaskit/editor-common/selection");
6
+ exports.getShouldMoveNode = exports.getPosWhenMoveNodeUp = exports.getPosWhenMoveNodeDown = exports.getNodeBoundsFromSelection = exports.canMoveNodeUpOrDown = void 0;
8
7
  var _state = require("@atlaskit/editor-prosemirror/state");
9
8
  var _utils = require("@atlaskit/editor-tables/utils");
10
9
  var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
11
- var _getNestedNodePosition = require("../../pm-plugins/utils/getNestedNodePosition");
12
- var getCurrentNodePosFromDragHandleSelection = exports.getCurrentNodePosFromDragHandleSelection = function getCurrentNodePosFromDragHandleSelection(_ref) {
13
- var selection = _ref.selection,
14
- schema = _ref.schema,
15
- resolve = _ref.resolve;
16
- var currentNodePos = -1;
17
- if (selection.empty && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true)) {
18
- currentNodePos = selection.$from.pos;
10
+ var _selection = require("../../pm-plugins/utils/selection");
11
+ /**
12
+ * Gets the current node position and bounds from the selection using the preserved selection logic.
13
+ * This ensures consistency with how the block controls plugin handles selection boundaries.
14
+ *
15
+ * Special handling for tables: When moving a table as a block, we need the outer table node's
16
+ * position, not the internal cell selection that createPreservedSelection returns.
17
+ *
18
+ * @param selection The current editor selection
19
+ * @returns An object with from and to positions, or undefined if selection is invalid
20
+ */
21
+ var getNodeBoundsFromSelection = exports.getNodeBoundsFromSelection = function getNodeBoundsFromSelection(selection) {
22
+ // Special case: if a table is selected, we want to move the entire table node
23
+ var tableInfo = (0, _utils.findTable)(selection);
24
+ if (tableInfo && (0, _utils.isTableSelected)(selection)) {
25
+ var tablePos = tableInfo.pos;
26
+ var tableTo = tablePos + tableInfo.node.nodeSize;
27
+ return {
28
+ from: tablePos,
29
+ to: tableTo
30
+ };
19
31
  }
20
- if ((0, _utils.isTableSelected)(selection)) {
21
- var _findTable$pos, _findTable;
22
- // We only move table node if it's fully selected
23
- // to avoid shortcut collision with table drag and drop
24
- currentNodePos = (_findTable$pos = (_findTable = (0, _utils.findTable)(selection)) === null || _findTable === void 0 ? void 0 : _findTable.pos) !== null && _findTable$pos !== void 0 ? _findTable$pos : currentNodePos;
25
- } else if (selection instanceof _state.NodeSelection && selection.node.type === schema.nodes.media && selection.node.attrs.type === 'file' && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true)) {
26
- // When a file is selected, it is actually wrapped in a media group
27
- // return the position of the media group , as the group is the node that we want to move up or down
28
- currentNodePos = selection.$from.pos - 1;
29
- } else if (!(selection instanceof _selection.GapCursorSelection)) {
30
- // 2. caret cursor is inside the node
31
- // 3. the start of the selection is inside the node
32
- currentNodePos = selection.$from.before(1);
33
- if (selection.$from.depth > 0) {
34
- currentNodePos = (0, _getNestedNodePosition.getNestedNodePosition)({
35
- selection: selection,
36
- schema: schema,
37
- resolve: resolve
38
- });
32
+
33
+ // Special case: if a media node (file) is selected, we need to get the parent mediaGroup
34
+ // This handles the case where clicking on a file creates a NodeSelection of the media node
35
+ // but we want to move the entire mediaGroup that wraps it
36
+ if (selection instanceof _state.NodeSelection && selection.node.type.name === 'media' && selection.node.attrs.type === 'file' && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true)) {
37
+ // The media node is wrapped in a mediaGroup, so we need to get the parent position
38
+ var mediaGroupPos = selection.$from.pos - 1;
39
+ var mediaGroupNode = selection.$from.doc.nodeAt(mediaGroupPos);
40
+ if (mediaGroupNode && mediaGroupNode.type.name === 'mediaGroup') {
41
+ return {
42
+ from: mediaGroupPos,
43
+ to: mediaGroupPos + mediaGroupNode.nodeSize
44
+ };
39
45
  }
40
46
  }
41
- return currentNodePos;
47
+
48
+ // Use createPreservedSelection to get properly expanded block boundaries
49
+ var preservedSelection = (0, _selection.createPreservedSelection)(selection.$from, selection.$to);
50
+ if (!preservedSelection) {
51
+ return undefined;
52
+ }
53
+ return {
54
+ from: preservedSelection.from,
55
+ to: preservedSelection.to
56
+ };
42
57
  };
43
58
  var getPosWhenMoveNodeUp = exports.getPosWhenMoveNodeUp = function getPosWhenMoveNodeUp($currentNodePos, currentNodePos) {
44
59
  var nodeIndex = $currentNodePos.index();
@@ -48,10 +63,10 @@ var getPosWhenMoveNodeUp = exports.getPosWhenMoveNodeUp = function getPosWhenMov
48
63
  }
49
64
  return nodeBefore ? currentNodePos - nodeBefore.nodeSize : -1;
50
65
  };
51
- var getPosWhenMoveNodeDown = exports.getPosWhenMoveNodeDown = function getPosWhenMoveNodeDown(_ref2) {
52
- var $currentNodePos = _ref2.$currentNodePos,
53
- nodeAfterPos = _ref2.nodeAfterPos,
54
- tr = _ref2.tr;
66
+ var getPosWhenMoveNodeDown = exports.getPosWhenMoveNodeDown = function getPosWhenMoveNodeDown(_ref) {
67
+ var $currentNodePos = _ref.$currentNodePos,
68
+ nodeAfterPos = _ref.nodeAfterPos,
69
+ tr = _ref.tr;
55
70
  var endOfDoc = $currentNodePos.end();
56
71
  if (nodeAfterPos > endOfDoc) {
57
72
  return -1;
@@ -68,27 +83,24 @@ var getPosWhenMoveNodeDown = exports.getPosWhenMoveNodeDown = function getPosWhe
68
83
  // if not the last node, move to the end of the next node
69
84
  return nodeAfter ? nodeAfterPos + nodeAfter.nodeSize : -1;
70
85
  };
71
- var getShouldMoveNode = exports.getShouldMoveNode = function getShouldMoveNode(_ref3) {
72
- var currentNodePos = _ref3.currentNodePos,
73
- moveToPos = _ref3.moveToPos,
74
- tr = _ref3.tr;
86
+ var getShouldMoveNode = exports.getShouldMoveNode = function getShouldMoveNode(_ref2) {
87
+ var currentNodePos = _ref2.currentNodePos,
88
+ moveToPos = _ref2.moveToPos,
89
+ tr = _ref2.tr;
75
90
  // only move the node if the destination is at the same depth, not support moving a nested node to a parent node
76
91
  return moveToPos > -1 && tr.doc.resolve(currentNodePos).depth === tr.doc.resolve(moveToPos).depth;
77
92
  };
78
93
  var canMoveNodeUpOrDown = exports.canMoveNodeUpOrDown = function canMoveNodeUpOrDown(tr) {
79
- var currentNodePos = getCurrentNodePosFromDragHandleSelection({
80
- selection: tr.selection,
81
- schema: tr.doc.type.schema,
82
- resolve: tr.doc.resolve.bind(tr.doc)
83
- });
84
- if (currentNodePos <= -1) {
94
+ var nodeBounds = getNodeBoundsFromSelection(tr.selection);
95
+ if (!nodeBounds || nodeBounds.from <= -1) {
85
96
  return {
86
97
  moveUp: false,
87
98
  moveDown: false
88
99
  };
89
100
  }
101
+ var currentNodePos = nodeBounds.from;
90
102
  var $currentNodePos = tr.doc.resolve(currentNodePos);
91
- var nodeAfterPos = $currentNodePos.posAtIndex($currentNodePos.index() + 1);
103
+ var nodeAfterPos = nodeBounds.to;
92
104
  var moveUpPos = getPosWhenMoveNodeUp($currentNodePos, currentNodePos);
93
105
  var moveDownPos = getPosWhenMoveNodeDown({
94
106
  $currentNodePos: $currentNodePos,
@@ -64,6 +64,11 @@ var handleMouseOver = exports.handleMouseOver = function handleMouseOver(view, e
64
64
  return false;
65
65
  }
66
66
 
67
+ // If the editor view is not in focus when the block menu is open, do not update the drag handle
68
+ if (!view.hasFocus() && isMenuOpen && (0, _expValEquals.expValEquals)('platform_editor_block_menu', 'isEnabled', true)) {
69
+ return false;
70
+ }
71
+
67
72
  // Most mouseover events don't fire during drag but some can slip through
68
73
  // when the drag begins. This prevents those.
69
74
  if (isDragging) {
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.createSelectionPreservationPlugin = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _bindEventListener = require("bind-event-listener");
9
10
  var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
10
11
  var _styles = require("@atlaskit/editor-common/styles");
11
12
  var _main = require("../main");
@@ -66,7 +67,7 @@ var createSelectionPreservationPlugin = exports.createSelectionPreservationPlugi
66
67
  newState.preservedSelection = (0, _selection.mapPreservedSelection)(newState.preservedSelection, tr);
67
68
  }
68
69
  if (!(0, _utils.compareSelections)(newState.preservedSelection, pluginState.preservedSelection)) {
69
- if (newState !== null && newState !== void 0 && newState.preservedSelection) {
70
+ if (newState.preservedSelection) {
70
71
  var _api$selection;
71
72
  api === null || api === void 0 || api.core.actions.execute(api === null || api === void 0 || (_api$selection = api.selection) === null || _api$selection === void 0 || (_api$selection = _api$selection.commands) === null || _api$selection === void 0 ? void 0 : _api$selection.setBlockSelection(newState.preservedSelection));
72
73
  } else {
@@ -85,8 +86,8 @@ var createSelectionPreservationPlugin = exports.createSelectionPreservationPlugi
85
86
  return null;
86
87
  }
87
88
 
88
- // Auto-stop if user explicitly changes selection or selection is set within a code block
89
- if ((0, _utils.hasUserSelectionChange)(transactions) || (0, _utils.isSelectionWithinCodeBlock)(stateSel)) {
89
+ // Auto-stop if user explicitly changes selection
90
+ if ((0, _utils.hasUserSelectionChange)(transactions)) {
90
91
  return (0, _editorCommands.stopPreservingSelection)({
91
92
  tr: newState.tr
92
93
  });
@@ -106,10 +107,51 @@ var createSelectionPreservationPlugin = exports.createSelectionPreservationPlugi
106
107
  }
107
108
  return newState.tr.setSelection(newSelection);
108
109
  },
109
- view: function view() {
110
+ view: function view(initialView) {
111
+ var view = initialView;
112
+ var unbindDocumentMouseDown = (0, _bindEventListener.bind)(document, {
113
+ type: 'mousedown',
114
+ listener: function listener(e) {
115
+ if (!(e.target instanceof HTMLElement)) {
116
+ return;
117
+ }
118
+ var _ref = _pluginKey.selectionPreservationPluginKey.getState(view.state) || {},
119
+ preservedSelection = _ref.preservedSelection;
120
+
121
+ // If there is no current preserved selection or the editor is not focused, do nothing
122
+ if (!preservedSelection) {
123
+ return;
124
+ }
125
+ var clickedDragHandle = !!e.target.closest(_styles.DRAG_HANDLE_SELECTOR);
126
+
127
+ // When mouse down on a drag handle we continue preserving the selection
128
+ if (clickedDragHandle) {
129
+ return;
130
+ }
131
+ var clickedOutsideEditor = !e.target.closest('.ProseMirror');
132
+
133
+ // When mouse down outside the editor continue to preserve the selection
134
+ if (clickedOutsideEditor) {
135
+ return;
136
+ }
137
+
138
+ // Otherwise mouse down anywhere else in the editor stops preserving the selection
139
+ var tr = view.state.tr;
140
+ (0, _editorCommands.stopPreservingSelection)({
141
+ tr: tr
142
+ });
143
+ view.dispatch(tr);
144
+ },
145
+ // Use capture phase to stop preservation before appendTransaction runs,
146
+ // preventing unwanted selection restoration when the user clicks into the editor.
147
+ options: {
148
+ capture: true
149
+ }
150
+ });
110
151
  return {
111
- update: function update(view, prevState) {
152
+ update: function update(updateView, prevState) {
112
153
  var _selectionPreservatio, _selectionPreservatio2, _key$getState, _key$getState2;
154
+ view = updateView;
113
155
  var prevPreservedSelection = (_selectionPreservatio = _pluginKey.selectionPreservationPluginKey.getState(prevState)) === null || _selectionPreservatio === void 0 ? void 0 : _selectionPreservatio.preservedSelection;
114
156
  var currPreservedSelection = (_selectionPreservatio2 = _pluginKey.selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio2 === void 0 ? void 0 : _selectionPreservatio2.preservedSelection;
115
157
  var prevActiveNode = (_key$getState = _main.key.getState(prevState)) === null || _key$getState === void 0 ? void 0 : _key$getState.activeNode;
@@ -117,33 +159,13 @@ var createSelectionPreservationPlugin = exports.createSelectionPreservationPlugi
117
159
  if (currPreservedSelection && view.hasFocus() && (!(0, _utils.compareSelections)(prevPreservedSelection, currPreservedSelection) || prevActiveNode !== currActiveNode)) {
118
160
  (0, _utils.syncDOMSelection)(view.state.selection);
119
161
  }
162
+ },
163
+ destroy: function destroy() {
164
+ unbindDocumentMouseDown();
120
165
  }
121
166
  };
122
167
  },
123
168
  props: {
124
- handleClick: function handleClick(view, pos, event) {
125
- var _ref = _pluginKey.selectionPreservationPluginKey.getState(view.state) || {},
126
- preservedSelection = _ref.preservedSelection;
127
-
128
- // If there is no current preserved selection, do nothing
129
- if (!preservedSelection) {
130
- return false;
131
- }
132
- var clickedDragHandle = event.target instanceof HTMLElement && event.target.closest(_styles.DRAG_HANDLE_SELECTOR);
133
-
134
- // When clicking a drag handle we continue preserving the selection
135
- if (!clickedDragHandle) {
136
- return false;
137
- }
138
-
139
- // Otherwise clicking anywhere else in the editor stops preserving the selection
140
- var tr = view.state.tr;
141
- (0, _editorCommands.stopPreservingSelection)({
142
- tr: tr
143
- });
144
- view.dispatch(tr);
145
- return false;
146
- },
147
169
  handleKeyDown: function handleKeyDown(view, event) {
148
170
  var _api$core, _api$blockControls;
149
171
  var _ref2 = _pluginKey.selectionPreservationPluginKey.getState(view.state) || {},
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.syncDOMSelection = exports.isSelectionWithinCodeBlock = exports.hasUserSelectionChange = exports.getSelectionPreservationMeta = exports.compareSelections = void 0;
6
+ exports.syncDOMSelection = 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.
@@ -20,18 +20,6 @@ var getSelectionPreservationMeta = exports.getSelectionPreservationMeta = functi
20
20
  return tr.getMeta(_pluginKey.selectionPreservationPluginKey);
21
21
  };
22
22
 
23
- /**
24
- * Checks if the current selection is within a code block.
25
- *
26
- * @param selection The current selection to check.
27
- * @returns True if the selection is within a code block, otherwise false.
28
- */
29
- var isSelectionWithinCodeBlock = exports.isSelectionWithinCodeBlock = function isSelectionWithinCodeBlock(_ref) {
30
- var $from = _ref.$from,
31
- $to = _ref.$to;
32
- return $from.sameParent($to) && $from.parent.type.name === 'codeBlock';
33
- };
34
-
35
23
  /**
36
24
  * Compares two selections for equality based on their from and to positions.
37
25
  *
@@ -96,11 +96,21 @@ var newGetSelection = exports.newGetSelection = function newGetSelection(doc, se
96
96
  var nodeSize = node ? node.nodeSize : 1;
97
97
  var nodeName = node === null || node === void 0 ? void 0 : node.type.name;
98
98
  if ((0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true)) {
99
+ var _doc$nodeAt;
99
100
  // if mediaGroup only has a single child, we want to select the child
100
101
  if (nodeName === 'mediaGroup' && (node === null || node === void 0 ? void 0 : node.childCount) === 1) {
101
102
  var $mediaStartPos = doc.resolve(start + 1);
102
103
  return new _state.NodeSelection($mediaStartPos);
103
104
  }
105
+
106
+ // if heading with alignment nested inside a layout column, return TextSelection
107
+ // As NodeSelection cause the desc.selectNode is not a function error in the syncNodeSelection in prosemirror view
108
+ // Results in block menu not open on the first 2 clicks for a heading with alignment nested inside a layout column
109
+ if (nodeName === 'heading' && node !== null && node !== void 0 && node.marks.some(function (mark) {
110
+ return mark.type.name === 'alignment';
111
+ }) && ((_doc$nodeAt = doc.nodeAt(start - 1)) === null || _doc$nodeAt === void 0 ? void 0 : _doc$nodeAt.type.name) === 'layoutColumn') {
112
+ return _state.TextSelection.create(doc, start, start + nodeSize);
113
+ }
104
114
  return new _state.NodeSelection(doc.resolve(start));
105
115
  }
106
116
 
@@ -4,7 +4,7 @@ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equ
4
4
  import { key } from '../pm-plugins/main';
5
5
  import { mapPreservedSelection } from '../pm-plugins/utils/selection';
6
6
  import { moveNode } from './move-node';
7
- import { canMoveNodeUpOrDown, getCurrentNodePosFromDragHandleSelection } from './utils/move-node-utils';
7
+ import { canMoveNodeUpOrDown, getNodeBoundsFromSelection } from './utils/move-node-utils';
8
8
  const getSelectionToIndex = (fromIndex, $to, depth) => {
9
9
  const toIndex = $to.index(depth);
10
10
  const toIndexAfter = $to.indexAfter(depth);
@@ -27,12 +27,12 @@ export const moveNodeWithBlockMenu = (api, direction) => {
27
27
  const preservedSelection = api === null || api === void 0 ? void 0 : (_api$blockControls$sh = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh === void 0 ? void 0 : _api$blockControls$sh.preservedSelection;
28
28
  const selection = preservedSelection !== null && preservedSelection !== void 0 ? preservedSelection : tr.selection;
29
29
 
30
- // Nodes like lists nest within themselves, we need to find the top most position
31
- const currentNodePos = getCurrentNodePosFromDragHandleSelection({
32
- selection,
33
- schema: tr.doc.type.schema,
34
- resolve: tr.doc.resolve.bind(tr.doc)
35
- });
30
+ // Use getNodeBoundsFromSelection to get the proper block boundaries
31
+ const nodeBounds = getNodeBoundsFromSelection(selection);
32
+ if (!nodeBounds) {
33
+ return tr;
34
+ }
35
+ const currentNodePos = nodeBounds.from;
36
36
  const {
37
37
  $from,
38
38
  $to
@@ -396,9 +396,6 @@ export const moveNode = api => (start, to, inputMethod = INPUT_METHOD.DRAG_AND_D
396
396
  if (!convertedNode) {
397
397
  return tr;
398
398
  }
399
- if ((sourceNode === null || sourceNode === void 0 ? void 0 : sourceNode.type.name) === 'taskList' && sliceFrom > 0 && expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
400
- sliceFrom = sliceFrom - 1;
401
- }
402
399
 
403
400
  // Currently we don't support breakout mark for children nodes of bodiedSyncBlock node
404
401
  // Hence strip out the mark for now
@@ -1,39 +1,54 @@
1
- import { GapCursorSelection } from '@atlaskit/editor-common/selection';
2
1
  import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
3
2
  import { findTable, isTableSelected } from '@atlaskit/editor-tables/utils';
4
3
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
5
- import { getNestedNodePosition } from '../../pm-plugins/utils/getNestedNodePosition';
6
- export const getCurrentNodePosFromDragHandleSelection = ({
7
- selection,
8
- schema,
9
- resolve
10
- }) => {
11
- let currentNodePos = -1;
12
- if (selection.empty && expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
13
- currentNodePos = selection.$from.pos;
4
+ import { createPreservedSelection } from '../../pm-plugins/utils/selection';
5
+
6
+ /**
7
+ * Gets the current node position and bounds from the selection using the preserved selection logic.
8
+ * This ensures consistency with how the block controls plugin handles selection boundaries.
9
+ *
10
+ * Special handling for tables: When moving a table as a block, we need the outer table node's
11
+ * position, not the internal cell selection that createPreservedSelection returns.
12
+ *
13
+ * @param selection The current editor selection
14
+ * @returns An object with from and to positions, or undefined if selection is invalid
15
+ */
16
+ export const getNodeBoundsFromSelection = selection => {
17
+ // Special case: if a table is selected, we want to move the entire table node
18
+ const tableInfo = findTable(selection);
19
+ if (tableInfo && isTableSelected(selection)) {
20
+ const tablePos = tableInfo.pos;
21
+ const tableTo = tablePos + tableInfo.node.nodeSize;
22
+ return {
23
+ from: tablePos,
24
+ to: tableTo
25
+ };
14
26
  }
15
- if (isTableSelected(selection)) {
16
- var _findTable$pos, _findTable;
17
- // We only move table node if it's fully selected
18
- // to avoid shortcut collision with table drag and drop
19
- currentNodePos = (_findTable$pos = (_findTable = findTable(selection)) === null || _findTable === void 0 ? void 0 : _findTable.pos) !== null && _findTable$pos !== void 0 ? _findTable$pos : currentNodePos;
20
- } else if (selection instanceof NodeSelection && selection.node.type === schema.nodes.media && selection.node.attrs.type === 'file' && expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
21
- // When a file is selected, it is actually wrapped in a media group
22
- // return the position of the media group , as the group is the node that we want to move up or down
23
- currentNodePos = selection.$from.pos - 1;
24
- } else if (!(selection instanceof GapCursorSelection)) {
25
- // 2. caret cursor is inside the node
26
- // 3. the start of the selection is inside the node
27
- currentNodePos = selection.$from.before(1);
28
- if (selection.$from.depth > 0) {
29
- currentNodePos = getNestedNodePosition({
30
- selection,
31
- schema,
32
- resolve
33
- });
27
+
28
+ // Special case: if a media node (file) is selected, we need to get the parent mediaGroup
29
+ // This handles the case where clicking on a file creates a NodeSelection of the media node
30
+ // but we want to move the entire mediaGroup that wraps it
31
+ if (selection instanceof NodeSelection && selection.node.type.name === 'media' && selection.node.attrs.type === 'file' && expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
32
+ // The media node is wrapped in a mediaGroup, so we need to get the parent position
33
+ const mediaGroupPos = selection.$from.pos - 1;
34
+ const mediaGroupNode = selection.$from.doc.nodeAt(mediaGroupPos);
35
+ if (mediaGroupNode && mediaGroupNode.type.name === 'mediaGroup') {
36
+ return {
37
+ from: mediaGroupPos,
38
+ to: mediaGroupPos + mediaGroupNode.nodeSize
39
+ };
34
40
  }
35
41
  }
36
- return currentNodePos;
42
+
43
+ // Use createPreservedSelection to get properly expanded block boundaries
44
+ const preservedSelection = createPreservedSelection(selection.$from, selection.$to);
45
+ if (!preservedSelection) {
46
+ return undefined;
47
+ }
48
+ return {
49
+ from: preservedSelection.from,
50
+ to: preservedSelection.to
51
+ };
37
52
  };
38
53
  export const getPosWhenMoveNodeUp = ($currentNodePos, currentNodePos) => {
39
54
  const nodeIndex = $currentNodePos.index();
@@ -73,19 +88,16 @@ export const getShouldMoveNode = ({
73
88
  return moveToPos > -1 && tr.doc.resolve(currentNodePos).depth === tr.doc.resolve(moveToPos).depth;
74
89
  };
75
90
  export const canMoveNodeUpOrDown = tr => {
76
- const currentNodePos = getCurrentNodePosFromDragHandleSelection({
77
- selection: tr.selection,
78
- schema: tr.doc.type.schema,
79
- resolve: tr.doc.resolve.bind(tr.doc)
80
- });
81
- if (currentNodePos <= -1) {
91
+ const nodeBounds = getNodeBoundsFromSelection(tr.selection);
92
+ if (!nodeBounds || nodeBounds.from <= -1) {
82
93
  return {
83
94
  moveUp: false,
84
95
  moveDown: false
85
96
  };
86
97
  }
98
+ const currentNodePos = nodeBounds.from;
87
99
  const $currentNodePos = tr.doc.resolve(currentNodePos);
88
- const nodeAfterPos = $currentNodePos.posAtIndex($currentNodePos.index() + 1);
100
+ const nodeAfterPos = nodeBounds.to;
89
101
  const moveUpPos = getPosWhenMoveNodeUp($currentNodePos, currentNodePos);
90
102
  const moveDownPos = getPosWhenMoveNodeDown({
91
103
  $currentNodePos,
@@ -56,6 +56,11 @@ export const handleMouseOver = (view, event, api) => {
56
56
  return false;
57
57
  }
58
58
 
59
+ // If the editor view is not in focus when the block menu is open, do not update the drag handle
60
+ if (!view.hasFocus() && isMenuOpen && expValEquals('platform_editor_block_menu', 'isEnabled', true)) {
61
+ return false;
62
+ }
63
+
59
64
  // Most mouseover events don't fire during drag but some can slip through
60
65
  // when the drag begins. This prevents those.
61
66
  if (isDragging) {
@@ -1,10 +1,11 @@
1
+ import { bind } from 'bind-event-listener';
1
2
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
3
  import { DRAG_HANDLE_SELECTOR } from '@atlaskit/editor-common/styles';
3
4
  import { key } from '../main';
4
5
  import { createPreservedSelection, mapPreservedSelection } from '../utils/selection';
5
6
  import { stopPreservingSelection } from './editor-commands';
6
7
  import { selectionPreservationPluginKey } from './plugin-key';
7
- import { compareSelections, getSelectionPreservationMeta, hasUserSelectionChange, isSelectionWithinCodeBlock, syncDOMSelection } from './utils';
8
+ import { compareSelections, getSelectionPreservationMeta, hasUserSelectionChange, syncDOMSelection } from './utils';
8
9
 
9
10
  /**
10
11
  * Selection Preservation Plugin
@@ -58,7 +59,7 @@ export const createSelectionPreservationPlugin = api => () => {
58
59
  newState.preservedSelection = mapPreservedSelection(newState.preservedSelection, tr);
59
60
  }
60
61
  if (!compareSelections(newState.preservedSelection, pluginState.preservedSelection)) {
61
- if (newState !== null && newState !== void 0 && newState.preservedSelection) {
62
+ if (newState.preservedSelection) {
62
63
  var _api$selection, _api$selection$comman;
63
64
  api === null || api === void 0 ? void 0 : api.core.actions.execute(api === null || api === void 0 ? void 0 : (_api$selection = api.selection) === null || _api$selection === void 0 ? void 0 : (_api$selection$comman = _api$selection.commands) === null || _api$selection$comman === void 0 ? void 0 : _api$selection$comman.setBlockSelection(newState.preservedSelection));
64
65
  } else {
@@ -77,8 +78,8 @@ export const createSelectionPreservationPlugin = api => () => {
77
78
  return null;
78
79
  }
79
80
 
80
- // Auto-stop if user explicitly changes selection or selection is set within a code block
81
- if (hasUserSelectionChange(transactions) || isSelectionWithinCodeBlock(stateSel)) {
81
+ // Auto-stop if user explicitly changes selection
82
+ if (hasUserSelectionChange(transactions)) {
82
83
  return stopPreservingSelection({
83
84
  tr: newState.tr
84
85
  });
@@ -98,10 +99,52 @@ export const createSelectionPreservationPlugin = api => () => {
98
99
  }
99
100
  return newState.tr.setSelection(newSelection);
100
101
  },
101
- view() {
102
+ view(initialView) {
103
+ let view = initialView;
104
+ const unbindDocumentMouseDown = bind(document, {
105
+ type: 'mousedown',
106
+ listener: e => {
107
+ if (!(e.target instanceof HTMLElement)) {
108
+ return;
109
+ }
110
+ const {
111
+ preservedSelection
112
+ } = selectionPreservationPluginKey.getState(view.state) || {};
113
+
114
+ // If there is no current preserved selection or the editor is not focused, do nothing
115
+ if (!preservedSelection) {
116
+ return;
117
+ }
118
+ const clickedDragHandle = !!e.target.closest(DRAG_HANDLE_SELECTOR);
119
+
120
+ // When mouse down on a drag handle we continue preserving the selection
121
+ if (clickedDragHandle) {
122
+ return;
123
+ }
124
+ const clickedOutsideEditor = !e.target.closest('.ProseMirror');
125
+
126
+ // When mouse down outside the editor continue to preserve the selection
127
+ if (clickedOutsideEditor) {
128
+ return;
129
+ }
130
+
131
+ // Otherwise mouse down anywhere else in the editor stops preserving the selection
132
+ const tr = view.state.tr;
133
+ stopPreservingSelection({
134
+ tr
135
+ });
136
+ view.dispatch(tr);
137
+ },
138
+ // Use capture phase to stop preservation before appendTransaction runs,
139
+ // preventing unwanted selection restoration when the user clicks into the editor.
140
+ options: {
141
+ capture: true
142
+ }
143
+ });
102
144
  return {
103
- update(view, prevState) {
145
+ update(updateView, prevState) {
104
146
  var _selectionPreservatio, _selectionPreservatio2, _key$getState, _key$getState2;
147
+ view = updateView;
105
148
  const prevPreservedSelection = (_selectionPreservatio = selectionPreservationPluginKey.getState(prevState)) === null || _selectionPreservatio === void 0 ? void 0 : _selectionPreservatio.preservedSelection;
106
149
  const currPreservedSelection = (_selectionPreservatio2 = selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio2 === void 0 ? void 0 : _selectionPreservatio2.preservedSelection;
107
150
  const prevActiveNode = (_key$getState = key.getState(prevState)) === null || _key$getState === void 0 ? void 0 : _key$getState.activeNode;
@@ -109,34 +152,13 @@ export const createSelectionPreservationPlugin = api => () => {
109
152
  if (currPreservedSelection && view.hasFocus() && (!compareSelections(prevPreservedSelection, currPreservedSelection) || prevActiveNode !== currActiveNode)) {
110
153
  syncDOMSelection(view.state.selection);
111
154
  }
155
+ },
156
+ destroy() {
157
+ unbindDocumentMouseDown();
112
158
  }
113
159
  };
114
160
  },
115
161
  props: {
116
- handleClick: (view, pos, event) => {
117
- const {
118
- preservedSelection
119
- } = selectionPreservationPluginKey.getState(view.state) || {};
120
-
121
- // If there is no current preserved selection, do nothing
122
- if (!preservedSelection) {
123
- return false;
124
- }
125
- const clickedDragHandle = event.target instanceof HTMLElement && event.target.closest(DRAG_HANDLE_SELECTOR);
126
-
127
- // When clicking a drag handle we continue preserving the selection
128
- if (!clickedDragHandle) {
129
- return false;
130
- }
131
-
132
- // Otherwise clicking anywhere else in the editor stops preserving the selection
133
- const tr = view.state.tr;
134
- stopPreservingSelection({
135
- tr
136
- });
137
- view.dispatch(tr);
138
- return false;
139
- },
140
162
  handleKeyDown: (view, event) => {
141
163
  var _api$core, _api$blockControls, _api$blockControls$co;
142
164
  const {
@@ -12,19 +12,6 @@ export const getSelectionPreservationMeta = tr => {
12
12
  return tr.getMeta(selectionPreservationPluginKey);
13
13
  };
14
14
 
15
- /**
16
- * Checks if the current selection is within a code block.
17
- *
18
- * @param selection The current selection to check.
19
- * @returns True if the selection is within a code block, otherwise false.
20
- */
21
- export const isSelectionWithinCodeBlock = ({
22
- $from,
23
- $to
24
- }) => {
25
- return $from.sameParent($to) && $from.parent.type.name === 'codeBlock';
26
- };
27
-
28
15
  /**
29
16
  * Compares two selections for equality based on their from and to positions.
30
17
  *
@@ -91,11 +91,19 @@ export const newGetSelection = (doc, selectionEmpty, start) => {
91
91
  const nodeSize = node ? node.nodeSize : 1;
92
92
  const nodeName = node === null || node === void 0 ? void 0 : node.type.name;
93
93
  if (expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
94
+ var _doc$nodeAt;
94
95
  // if mediaGroup only has a single child, we want to select the child
95
96
  if (nodeName === 'mediaGroup' && (node === null || node === void 0 ? void 0 : node.childCount) === 1) {
96
97
  const $mediaStartPos = doc.resolve(start + 1);
97
98
  return new NodeSelection($mediaStartPos);
98
99
  }
100
+
101
+ // if heading with alignment nested inside a layout column, return TextSelection
102
+ // As NodeSelection cause the desc.selectNode is not a function error in the syncNodeSelection in prosemirror view
103
+ // Results in block menu not open on the first 2 clicks for a heading with alignment nested inside a layout column
104
+ if (nodeName === 'heading' && node !== null && node !== void 0 && node.marks.some(mark => mark.type.name === 'alignment') && ((_doc$nodeAt = doc.nodeAt(start - 1)) === null || _doc$nodeAt === void 0 ? void 0 : _doc$nodeAt.type.name) === 'layoutColumn') {
105
+ return TextSelection.create(doc, start, start + nodeSize);
106
+ }
99
107
  return new NodeSelection(doc.resolve(start));
100
108
  }
101
109
 
@@ -7,7 +7,7 @@ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equ
7
7
  import { key } from '../pm-plugins/main';
8
8
  import { mapPreservedSelection } from '../pm-plugins/utils/selection';
9
9
  import { moveNode } from './move-node';
10
- import { canMoveNodeUpOrDown, getCurrentNodePosFromDragHandleSelection } from './utils/move-node-utils';
10
+ import { canMoveNodeUpOrDown, getNodeBoundsFromSelection } from './utils/move-node-utils';
11
11
  var getSelectionToIndex = function getSelectionToIndex(fromIndex, $to, depth) {
12
12
  var toIndex = $to.index(depth);
13
13
  var toIndexAfter = $to.indexAfter(depth);
@@ -29,12 +29,12 @@ export var moveNodeWithBlockMenu = function moveNodeWithBlockMenu(api, direction
29
29
  var preservedSelection = api === null || api === void 0 || (_api$blockControls$sh = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh === void 0 ? void 0 : _api$blockControls$sh.preservedSelection;
30
30
  var selection = preservedSelection !== null && preservedSelection !== void 0 ? preservedSelection : tr.selection;
31
31
 
32
- // Nodes like lists nest within themselves, we need to find the top most position
33
- var currentNodePos = getCurrentNodePosFromDragHandleSelection({
34
- selection: selection,
35
- schema: tr.doc.type.schema,
36
- resolve: tr.doc.resolve.bind(tr.doc)
37
- });
32
+ // Use getNodeBoundsFromSelection to get the proper block boundaries
33
+ var nodeBounds = getNodeBoundsFromSelection(selection);
34
+ if (!nodeBounds) {
35
+ return tr;
36
+ }
37
+ var currentNodePos = nodeBounds.from;
38
38
  var $from = selection.$from,
39
39
  $to = selection.$to;
40
40
  var depth = tr.doc.resolve(currentNodePos).depth;
@@ -388,9 +388,6 @@ export var moveNode = function moveNode(api) {
388
388
  if (!convertedNode) {
389
389
  return tr;
390
390
  }
391
- if ((sourceNode === null || sourceNode === void 0 ? void 0 : sourceNode.type.name) === 'taskList' && sliceFrom > 0 && expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
392
- sliceFrom = sliceFrom - 1;
393
- }
394
391
 
395
392
  // Currently we don't support breakout mark for children nodes of bodiedSyncBlock node
396
393
  // Hence strip out the mark for now
@@ -1,38 +1,54 @@
1
- import { GapCursorSelection } from '@atlaskit/editor-common/selection';
2
1
  import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
3
2
  import { findTable, isTableSelected } from '@atlaskit/editor-tables/utils';
4
3
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
5
- import { getNestedNodePosition } from '../../pm-plugins/utils/getNestedNodePosition';
6
- export var getCurrentNodePosFromDragHandleSelection = function getCurrentNodePosFromDragHandleSelection(_ref) {
7
- var selection = _ref.selection,
8
- schema = _ref.schema,
9
- resolve = _ref.resolve;
10
- var currentNodePos = -1;
11
- if (selection.empty && expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
12
- currentNodePos = selection.$from.pos;
4
+ import { createPreservedSelection } from '../../pm-plugins/utils/selection';
5
+
6
+ /**
7
+ * Gets the current node position and bounds from the selection using the preserved selection logic.
8
+ * This ensures consistency with how the block controls plugin handles selection boundaries.
9
+ *
10
+ * Special handling for tables: When moving a table as a block, we need the outer table node's
11
+ * position, not the internal cell selection that createPreservedSelection returns.
12
+ *
13
+ * @param selection The current editor selection
14
+ * @returns An object with from and to positions, or undefined if selection is invalid
15
+ */
16
+ export var getNodeBoundsFromSelection = function getNodeBoundsFromSelection(selection) {
17
+ // Special case: if a table is selected, we want to move the entire table node
18
+ var tableInfo = findTable(selection);
19
+ if (tableInfo && isTableSelected(selection)) {
20
+ var tablePos = tableInfo.pos;
21
+ var tableTo = tablePos + tableInfo.node.nodeSize;
22
+ return {
23
+ from: tablePos,
24
+ to: tableTo
25
+ };
13
26
  }
14
- if (isTableSelected(selection)) {
15
- var _findTable$pos, _findTable;
16
- // We only move table node if it's fully selected
17
- // to avoid shortcut collision with table drag and drop
18
- currentNodePos = (_findTable$pos = (_findTable = findTable(selection)) === null || _findTable === void 0 ? void 0 : _findTable.pos) !== null && _findTable$pos !== void 0 ? _findTable$pos : currentNodePos;
19
- } else if (selection instanceof NodeSelection && selection.node.type === schema.nodes.media && selection.node.attrs.type === 'file' && expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
20
- // When a file is selected, it is actually wrapped in a media group
21
- // return the position of the media group , as the group is the node that we want to move up or down
22
- currentNodePos = selection.$from.pos - 1;
23
- } else if (!(selection instanceof GapCursorSelection)) {
24
- // 2. caret cursor is inside the node
25
- // 3. the start of the selection is inside the node
26
- currentNodePos = selection.$from.before(1);
27
- if (selection.$from.depth > 0) {
28
- currentNodePos = getNestedNodePosition({
29
- selection: selection,
30
- schema: schema,
31
- resolve: resolve
32
- });
27
+
28
+ // Special case: if a media node (file) is selected, we need to get the parent mediaGroup
29
+ // This handles the case where clicking on a file creates a NodeSelection of the media node
30
+ // but we want to move the entire mediaGroup that wraps it
31
+ if (selection instanceof NodeSelection && selection.node.type.name === 'media' && selection.node.attrs.type === 'file' && expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
32
+ // The media node is wrapped in a mediaGroup, so we need to get the parent position
33
+ var mediaGroupPos = selection.$from.pos - 1;
34
+ var mediaGroupNode = selection.$from.doc.nodeAt(mediaGroupPos);
35
+ if (mediaGroupNode && mediaGroupNode.type.name === 'mediaGroup') {
36
+ return {
37
+ from: mediaGroupPos,
38
+ to: mediaGroupPos + mediaGroupNode.nodeSize
39
+ };
33
40
  }
34
41
  }
35
- return currentNodePos;
42
+
43
+ // Use createPreservedSelection to get properly expanded block boundaries
44
+ var preservedSelection = createPreservedSelection(selection.$from, selection.$to);
45
+ if (!preservedSelection) {
46
+ return undefined;
47
+ }
48
+ return {
49
+ from: preservedSelection.from,
50
+ to: preservedSelection.to
51
+ };
36
52
  };
37
53
  export var getPosWhenMoveNodeUp = function getPosWhenMoveNodeUp($currentNodePos, currentNodePos) {
38
54
  var nodeIndex = $currentNodePos.index();
@@ -42,10 +58,10 @@ export var getPosWhenMoveNodeUp = function getPosWhenMoveNodeUp($currentNodePos,
42
58
  }
43
59
  return nodeBefore ? currentNodePos - nodeBefore.nodeSize : -1;
44
60
  };
45
- export var getPosWhenMoveNodeDown = function getPosWhenMoveNodeDown(_ref2) {
46
- var $currentNodePos = _ref2.$currentNodePos,
47
- nodeAfterPos = _ref2.nodeAfterPos,
48
- tr = _ref2.tr;
61
+ export var getPosWhenMoveNodeDown = function getPosWhenMoveNodeDown(_ref) {
62
+ var $currentNodePos = _ref.$currentNodePos,
63
+ nodeAfterPos = _ref.nodeAfterPos,
64
+ tr = _ref.tr;
49
65
  var endOfDoc = $currentNodePos.end();
50
66
  if (nodeAfterPos > endOfDoc) {
51
67
  return -1;
@@ -62,27 +78,24 @@ export var getPosWhenMoveNodeDown = function getPosWhenMoveNodeDown(_ref2) {
62
78
  // if not the last node, move to the end of the next node
63
79
  return nodeAfter ? nodeAfterPos + nodeAfter.nodeSize : -1;
64
80
  };
65
- export var getShouldMoveNode = function getShouldMoveNode(_ref3) {
66
- var currentNodePos = _ref3.currentNodePos,
67
- moveToPos = _ref3.moveToPos,
68
- tr = _ref3.tr;
81
+ export var getShouldMoveNode = function getShouldMoveNode(_ref2) {
82
+ var currentNodePos = _ref2.currentNodePos,
83
+ moveToPos = _ref2.moveToPos,
84
+ tr = _ref2.tr;
69
85
  // only move the node if the destination is at the same depth, not support moving a nested node to a parent node
70
86
  return moveToPos > -1 && tr.doc.resolve(currentNodePos).depth === tr.doc.resolve(moveToPos).depth;
71
87
  };
72
88
  export var canMoveNodeUpOrDown = function canMoveNodeUpOrDown(tr) {
73
- var currentNodePos = getCurrentNodePosFromDragHandleSelection({
74
- selection: tr.selection,
75
- schema: tr.doc.type.schema,
76
- resolve: tr.doc.resolve.bind(tr.doc)
77
- });
78
- if (currentNodePos <= -1) {
89
+ var nodeBounds = getNodeBoundsFromSelection(tr.selection);
90
+ if (!nodeBounds || nodeBounds.from <= -1) {
79
91
  return {
80
92
  moveUp: false,
81
93
  moveDown: false
82
94
  };
83
95
  }
96
+ var currentNodePos = nodeBounds.from;
84
97
  var $currentNodePos = tr.doc.resolve(currentNodePos);
85
- var nodeAfterPos = $currentNodePos.posAtIndex($currentNodePos.index() + 1);
98
+ var nodeAfterPos = nodeBounds.to;
86
99
  var moveUpPos = getPosWhenMoveNodeUp($currentNodePos, currentNodePos);
87
100
  var moveDownPos = getPosWhenMoveNodeDown({
88
101
  $currentNodePos: $currentNodePos,
@@ -57,6 +57,11 @@ export var handleMouseOver = function handleMouseOver(view, event, api) {
57
57
  return false;
58
58
  }
59
59
 
60
+ // If the editor view is not in focus when the block menu is open, do not update the drag handle
61
+ if (!view.hasFocus() && isMenuOpen && expValEquals('platform_editor_block_menu', 'isEnabled', true)) {
62
+ return false;
63
+ }
64
+
60
65
  // Most mouseover events don't fire during drag but some can slip through
61
66
  // when the drag begins. This prevents those.
62
67
  if (isDragging) {
@@ -1,13 +1,14 @@
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 { bind } from 'bind-event-listener';
4
5
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
5
6
  import { DRAG_HANDLE_SELECTOR } from '@atlaskit/editor-common/styles';
6
7
  import { key } from '../main';
7
8
  import { createPreservedSelection, mapPreservedSelection } from '../utils/selection';
8
9
  import { stopPreservingSelection } from './editor-commands';
9
10
  import { selectionPreservationPluginKey } from './plugin-key';
10
- import { compareSelections, getSelectionPreservationMeta, hasUserSelectionChange, isSelectionWithinCodeBlock, syncDOMSelection } from './utils';
11
+ import { compareSelections, getSelectionPreservationMeta, hasUserSelectionChange, syncDOMSelection } from './utils';
11
12
 
12
13
  /**
13
14
  * Selection Preservation Plugin
@@ -60,7 +61,7 @@ export var createSelectionPreservationPlugin = function createSelectionPreservat
60
61
  newState.preservedSelection = mapPreservedSelection(newState.preservedSelection, tr);
61
62
  }
62
63
  if (!compareSelections(newState.preservedSelection, pluginState.preservedSelection)) {
63
- if (newState !== null && newState !== void 0 && newState.preservedSelection) {
64
+ if (newState.preservedSelection) {
64
65
  var _api$selection;
65
66
  api === null || api === void 0 || api.core.actions.execute(api === null || api === void 0 || (_api$selection = api.selection) === null || _api$selection === void 0 || (_api$selection = _api$selection.commands) === null || _api$selection === void 0 ? void 0 : _api$selection.setBlockSelection(newState.preservedSelection));
66
67
  } else {
@@ -79,8 +80,8 @@ export var createSelectionPreservationPlugin = function createSelectionPreservat
79
80
  return null;
80
81
  }
81
82
 
82
- // Auto-stop if user explicitly changes selection or selection is set within a code block
83
- if (hasUserSelectionChange(transactions) || isSelectionWithinCodeBlock(stateSel)) {
83
+ // Auto-stop if user explicitly changes selection
84
+ if (hasUserSelectionChange(transactions)) {
84
85
  return stopPreservingSelection({
85
86
  tr: newState.tr
86
87
  });
@@ -100,10 +101,51 @@ export var createSelectionPreservationPlugin = function createSelectionPreservat
100
101
  }
101
102
  return newState.tr.setSelection(newSelection);
102
103
  },
103
- view: function view() {
104
+ view: function view(initialView) {
105
+ var view = initialView;
106
+ var unbindDocumentMouseDown = bind(document, {
107
+ type: 'mousedown',
108
+ listener: function listener(e) {
109
+ if (!(e.target instanceof HTMLElement)) {
110
+ return;
111
+ }
112
+ var _ref = selectionPreservationPluginKey.getState(view.state) || {},
113
+ preservedSelection = _ref.preservedSelection;
114
+
115
+ // If there is no current preserved selection or the editor is not focused, do nothing
116
+ if (!preservedSelection) {
117
+ return;
118
+ }
119
+ var clickedDragHandle = !!e.target.closest(DRAG_HANDLE_SELECTOR);
120
+
121
+ // When mouse down on a drag handle we continue preserving the selection
122
+ if (clickedDragHandle) {
123
+ return;
124
+ }
125
+ var clickedOutsideEditor = !e.target.closest('.ProseMirror');
126
+
127
+ // When mouse down outside the editor continue to preserve the selection
128
+ if (clickedOutsideEditor) {
129
+ return;
130
+ }
131
+
132
+ // Otherwise mouse down anywhere else in the editor stops preserving the selection
133
+ var tr = view.state.tr;
134
+ stopPreservingSelection({
135
+ tr: tr
136
+ });
137
+ view.dispatch(tr);
138
+ },
139
+ // Use capture phase to stop preservation before appendTransaction runs,
140
+ // preventing unwanted selection restoration when the user clicks into the editor.
141
+ options: {
142
+ capture: true
143
+ }
144
+ });
104
145
  return {
105
- update: function update(view, prevState) {
146
+ update: function update(updateView, prevState) {
106
147
  var _selectionPreservatio, _selectionPreservatio2, _key$getState, _key$getState2;
148
+ view = updateView;
107
149
  var prevPreservedSelection = (_selectionPreservatio = selectionPreservationPluginKey.getState(prevState)) === null || _selectionPreservatio === void 0 ? void 0 : _selectionPreservatio.preservedSelection;
108
150
  var currPreservedSelection = (_selectionPreservatio2 = selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio2 === void 0 ? void 0 : _selectionPreservatio2.preservedSelection;
109
151
  var prevActiveNode = (_key$getState = key.getState(prevState)) === null || _key$getState === void 0 ? void 0 : _key$getState.activeNode;
@@ -111,33 +153,13 @@ export var createSelectionPreservationPlugin = function createSelectionPreservat
111
153
  if (currPreservedSelection && view.hasFocus() && (!compareSelections(prevPreservedSelection, currPreservedSelection) || prevActiveNode !== currActiveNode)) {
112
154
  syncDOMSelection(view.state.selection);
113
155
  }
156
+ },
157
+ destroy: function destroy() {
158
+ unbindDocumentMouseDown();
114
159
  }
115
160
  };
116
161
  },
117
162
  props: {
118
- handleClick: function handleClick(view, pos, event) {
119
- var _ref = selectionPreservationPluginKey.getState(view.state) || {},
120
- preservedSelection = _ref.preservedSelection;
121
-
122
- // If there is no current preserved selection, do nothing
123
- if (!preservedSelection) {
124
- return false;
125
- }
126
- var clickedDragHandle = event.target instanceof HTMLElement && event.target.closest(DRAG_HANDLE_SELECTOR);
127
-
128
- // When clicking a drag handle we continue preserving the selection
129
- if (!clickedDragHandle) {
130
- return false;
131
- }
132
-
133
- // Otherwise clicking anywhere else in the editor stops preserving the selection
134
- var tr = view.state.tr;
135
- stopPreservingSelection({
136
- tr: tr
137
- });
138
- view.dispatch(tr);
139
- return false;
140
- },
141
163
  handleKeyDown: function handleKeyDown(view, event) {
142
164
  var _api$core, _api$blockControls;
143
165
  var _ref2 = selectionPreservationPluginKey.getState(view.state) || {},
@@ -14,18 +14,6 @@ export var getSelectionPreservationMeta = function getSelectionPreservationMeta(
14
14
  return tr.getMeta(selectionPreservationPluginKey);
15
15
  };
16
16
 
17
- /**
18
- * Checks if the current selection is within a code block.
19
- *
20
- * @param selection The current selection to check.
21
- * @returns True if the selection is within a code block, otherwise false.
22
- */
23
- export var isSelectionWithinCodeBlock = function isSelectionWithinCodeBlock(_ref) {
24
- var $from = _ref.$from,
25
- $to = _ref.$to;
26
- return $from.sameParent($to) && $from.parent.type.name === 'codeBlock';
27
- };
28
-
29
17
  /**
30
18
  * Compares two selections for equality based on their from and to positions.
31
19
  *
@@ -90,11 +90,21 @@ export var newGetSelection = function newGetSelection(doc, selectionEmpty, start
90
90
  var nodeSize = node ? node.nodeSize : 1;
91
91
  var nodeName = node === null || node === void 0 ? void 0 : node.type.name;
92
92
  if (expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
93
+ var _doc$nodeAt;
93
94
  // if mediaGroup only has a single child, we want to select the child
94
95
  if (nodeName === 'mediaGroup' && (node === null || node === void 0 ? void 0 : node.childCount) === 1) {
95
96
  var $mediaStartPos = doc.resolve(start + 1);
96
97
  return new NodeSelection($mediaStartPos);
97
98
  }
99
+
100
+ // if heading with alignment nested inside a layout column, return TextSelection
101
+ // As NodeSelection cause the desc.selectNode is not a function error in the syncNodeSelection in prosemirror view
102
+ // Results in block menu not open on the first 2 clicks for a heading with alignment nested inside a layout column
103
+ if (nodeName === 'heading' && node !== null && node !== void 0 && node.marks.some(function (mark) {
104
+ return mark.type.name === 'alignment';
105
+ }) && ((_doc$nodeAt = doc.nodeAt(start - 1)) === null || _doc$nodeAt === void 0 ? void 0 : _doc$nodeAt.type.name) === 'layoutColumn') {
106
+ return TextSelection.create(doc, start, start + nodeSize);
107
+ }
98
108
  return new NodeSelection(doc.resolve(start));
99
109
  }
100
110
 
@@ -1,11 +1,19 @@
1
- import type { ResolvedPos, Schema } from '@atlaskit/editor-prosemirror/model';
2
- import type { Transaction } from '@atlaskit/editor-prosemirror/state';
3
- import { type Selection } from '@atlaskit/editor-prosemirror/state';
4
- export declare const getCurrentNodePosFromDragHandleSelection: ({ selection, schema, resolve, }: {
5
- resolve: (pos: number) => ResolvedPos;
6
- schema: Schema;
7
- selection: Selection;
8
- }) => number;
1
+ import type { ResolvedPos } from '@atlaskit/editor-prosemirror/model';
2
+ import { type Selection, type Transaction } from '@atlaskit/editor-prosemirror/state';
3
+ /**
4
+ * Gets the current node position and bounds from the selection using the preserved selection logic.
5
+ * This ensures consistency with how the block controls plugin handles selection boundaries.
6
+ *
7
+ * Special handling for tables: When moving a table as a block, we need the outer table node's
8
+ * position, not the internal cell selection that createPreservedSelection returns.
9
+ *
10
+ * @param selection The current editor selection
11
+ * @returns An object with from and to positions, or undefined if selection is invalid
12
+ */
13
+ export declare const getNodeBoundsFromSelection: (selection: Selection) => {
14
+ from: number;
15
+ to: number;
16
+ } | undefined;
9
17
  export declare const getPosWhenMoveNodeUp: ($currentNodePos: ResolvedPos, currentNodePos: number) => number;
10
18
  export declare const getPosWhenMoveNodeDown: ({ $currentNodePos, nodeAfterPos, tr, }: {
11
19
  $currentNodePos: ResolvedPos;
@@ -8,13 +8,6 @@ import type { SelectionPreservationMeta } from './types';
8
8
  */
9
9
  export declare const hasUserSelectionChange: (transactions: readonly Transaction[]) => boolean;
10
10
  export declare const getSelectionPreservationMeta: (tr: Transaction | ReadonlyTransaction) => SelectionPreservationMeta | undefined;
11
- /**
12
- * Checks if the current selection is within a code block.
13
- *
14
- * @param selection The current selection to check.
15
- * @returns True if the selection is within a code block, otherwise false.
16
- */
17
- export declare const isSelectionWithinCodeBlock: ({ $from, $to }: Selection) => boolean;
18
11
  /**
19
12
  * Compares two selections for equality based on their from and to positions.
20
13
  *
@@ -1,11 +1,19 @@
1
- import type { ResolvedPos, Schema } from '@atlaskit/editor-prosemirror/model';
2
- import type { Transaction } from '@atlaskit/editor-prosemirror/state';
3
- import { type Selection } from '@atlaskit/editor-prosemirror/state';
4
- export declare const getCurrentNodePosFromDragHandleSelection: ({ selection, schema, resolve, }: {
5
- resolve: (pos: number) => ResolvedPos;
6
- schema: Schema;
7
- selection: Selection;
8
- }) => number;
1
+ import type { ResolvedPos } from '@atlaskit/editor-prosemirror/model';
2
+ import { type Selection, type Transaction } from '@atlaskit/editor-prosemirror/state';
3
+ /**
4
+ * Gets the current node position and bounds from the selection using the preserved selection logic.
5
+ * This ensures consistency with how the block controls plugin handles selection boundaries.
6
+ *
7
+ * Special handling for tables: When moving a table as a block, we need the outer table node's
8
+ * position, not the internal cell selection that createPreservedSelection returns.
9
+ *
10
+ * @param selection The current editor selection
11
+ * @returns An object with from and to positions, or undefined if selection is invalid
12
+ */
13
+ export declare const getNodeBoundsFromSelection: (selection: Selection) => {
14
+ from: number;
15
+ to: number;
16
+ } | undefined;
9
17
  export declare const getPosWhenMoveNodeUp: ($currentNodePos: ResolvedPos, currentNodePos: number) => number;
10
18
  export declare const getPosWhenMoveNodeDown: ({ $currentNodePos, nodeAfterPos, tr, }: {
11
19
  $currentNodePos: ResolvedPos;
@@ -8,13 +8,6 @@ import type { SelectionPreservationMeta } from './types';
8
8
  */
9
9
  export declare const hasUserSelectionChange: (transactions: readonly Transaction[]) => boolean;
10
10
  export declare const getSelectionPreservationMeta: (tr: Transaction | ReadonlyTransaction) => SelectionPreservationMeta | undefined;
11
- /**
12
- * Checks if the current selection is within a code block.
13
- *
14
- * @param selection The current selection to check.
15
- * @returns True if the selection is within a code block, otherwise false.
16
- */
17
- export declare const isSelectionWithinCodeBlock: ({ $from, $to }: Selection) => boolean;
18
11
  /**
19
12
  * Compares two selections for equality based on their from and to positions.
20
13
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-block-controls",
3
- "version": "8.0.9",
3
+ "version": "8.0.11",
4
4
  "description": "Block controls plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -54,7 +54,7 @@
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.15.0",
57
+ "@atlaskit/tmp-editor-statsig": "^16.19.0",
58
58
  "@atlaskit/tokens": "^9.1.0",
59
59
  "@atlaskit/tooltip": "^20.14.0",
60
60
  "@babel/runtime": "^7.0.0",
@@ -66,7 +66,7 @@
66
66
  "uuid": "^3.1.0"
67
67
  },
68
68
  "peerDependencies": {
69
- "@atlaskit/editor-common": "^111.7.0",
69
+ "@atlaskit/editor-common": "^111.8.0",
70
70
  "react": "^18.2.0",
71
71
  "react-dom": "^18.2.0",
72
72
  "react-intl-next": "npm:react-intl@^5.18.1"