@atlaskit/editor-plugin-block-controls 7.19.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @atlaskit/editor-plugin-block-controls
2
2
 
3
+ ## 8.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - [`4da819b186eaf`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/4da819b186eaf) -
8
+ EDITOR-3911 selection preservation key handling
9
+ - Updated dependencies
10
+
3
11
  ## 7.19.0
4
12
 
5
13
  ### Minor Changes
@@ -13,6 +13,7 @@ var _state = require("@atlaskit/editor-prosemirror/state");
13
13
  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
+ var _handleKeyDownWithPreservedSelection = require("./editor-commands/handle-key-down-with-preserved-selection");
16
17
  var _moveNode = require("./editor-commands/move-node");
17
18
  var _moveNodeWithBlockMenu2 = require("./editor-commands/move-node-with-block-menu");
18
19
  var _moveToLayout = require("./editor-commands/move-to-layout");
@@ -102,9 +103,6 @@ var blockControlsPlugin = exports.blockControlsPlugin = function blockControlsPl
102
103
  tr: tr
103
104
  });
104
105
  }
105
- (0, _editorCommands.stopPreservingSelection)({
106
- tr: tr
107
- });
108
106
  return tr;
109
107
  }
110
108
 
@@ -249,6 +247,7 @@ var blockControlsPlugin = exports.blockControlsPlugin = function blockControlsPl
249
247
  moveNodeWithBlockMenu: function moveNodeWithBlockMenu(direction) {
250
248
  return (0, _moveNodeWithBlockMenu2.moveNodeWithBlockMenu)(api, direction);
251
249
  },
250
+ handleKeyDownWithPreservedSelection: (0, _handleKeyDownWithPreservedSelection.handleKeyDownWithPreservedSelection)(api),
252
251
  startPreservingSelection: function startPreservingSelection() {
253
252
  return _editorCommands.startPreservingSelection;
254
253
  },
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.handleKeyDownWithPreservedSelection = void 0;
7
+ var _selection = require("@atlaskit/editor-common/selection");
8
+ var _editorCommands = require("../pm-plugins/selection-preservation/editor-commands");
9
+ var getKeyboardEventInfo = function getKeyboardEventInfo(event) {
10
+ var key = event.key.toLowerCase();
11
+ var isMetaCtrl = event.metaKey || event.ctrlKey;
12
+ var isDelete = ['backspace', 'delete'].includes(key);
13
+ var isCut = isMetaCtrl && key === 'x';
14
+ var isPaste = isMetaCtrl && key === 'v';
15
+ var isDestructive = isDelete || isCut || isPaste;
16
+ var isModifierOnly = ['control', 'meta', 'alt', 'shift'].includes(key) && !isMetaCtrl;
17
+ var isCopy = isMetaCtrl && key === 'c';
18
+ var isInert = isModifierOnly || isCopy;
19
+ return {
20
+ isDelete: isDelete,
21
+ isDestructive: isDestructive,
22
+ isInert: isInert
23
+ };
24
+ };
25
+
26
+ /**
27
+ * Handles key presses when a selection is being preserved, when the block menu is open or closed.
28
+ *
29
+ * Based on the key pressed and whether the block menu is open or closed:
30
+ * 1. Handles key presses with custom logic (e.g. delete/backspace)
31
+ * 2. Closes the block menu
32
+ * 3. Stops preserving the selection
33
+ *
34
+ * This is used in two places:
35
+ * 1. selection preservation plugin when selection is being preserved, and focus is in the editor.
36
+ * 2. block menu UI component when focus is in the block menu.
37
+ *
38
+ * Ensures consistent behaviour for key presses in both scenarios.
39
+ */
40
+ var handleKeyDownWithPreservedSelection = exports.handleKeyDownWithPreservedSelection = function handleKeyDownWithPreservedSelection(api) {
41
+ return function (event) {
42
+ return function (_ref) {
43
+ var _api$userIntent;
44
+ var tr = _ref.tr;
45
+ if (!api) {
46
+ return tr;
47
+ }
48
+ var _getKeyboardEventInfo = getKeyboardEventInfo(event),
49
+ isDelete = _getKeyboardEventInfo.isDelete,
50
+ isDestructive = _getKeyboardEventInfo.isDestructive,
51
+ isInert = _getKeyboardEventInfo.isInert;
52
+
53
+ // Handle delete/backspace key presses with custom logic to ensure preserved selection is used
54
+ if (isDelete) {
55
+ var _api$blockControls;
56
+ tr = (0, _selection.deleteSelectedRange)(tr, (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.sharedState.currentState()) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.preservedSelection);
57
+ }
58
+ var isBlockMenuOpen = ((_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || (_api$userIntent = _api$userIntent.sharedState.currentState()) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.currentUserIntent) === 'blockMenuOpen';
59
+
60
+ // When selected content is being removed and the block menu is open
61
+ // close the block menu and refocus the editor
62
+ var shouldCloseBlockMenu = isDestructive && isBlockMenuOpen;
63
+ if (shouldCloseBlockMenu) {
64
+ var _api$blockControls2;
65
+ (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || _api$blockControls2.commands.toggleBlockMenu({
66
+ closeMenu: true
67
+ })({
68
+ tr: tr
69
+ });
70
+ api.core.actions.focus({
71
+ scrollIntoView: false
72
+ });
73
+ }
74
+
75
+ // Stop preserving when:
76
+ // 1. Content is being removed (delete/cut/paste) OR
77
+ // 2. Menu is closed AND user pressed a non-inert key (i.e. action which modifies selection or content)
78
+ var shouldStopPreservingSelection = isDestructive || !isBlockMenuOpen && !isInert;
79
+ if (shouldStopPreservingSelection) {
80
+ (0, _editorCommands.stopPreservingSelection)({
81
+ tr: tr
82
+ });
83
+ }
84
+ return tr;
85
+ };
86
+ };
87
+ };
@@ -75,7 +75,9 @@ var createSelectionPreservationPlugin = exports.createSelectionPreservationPlugi
75
75
  if (!savedSel) {
76
76
  return null;
77
77
  }
78
- if ((0, _utils.hasUserSelectionChange)(transactions)) {
78
+
79
+ // Auto-stop if user explicitly changes selection or selection is set within a code block
80
+ if ((0, _utils.hasUserSelectionChange)(transactions) || (0, _utils.isSelectionWithinCodeBlock)(newState.selection)) {
79
81
  // Auto-stop if user explicitly changes selection
80
82
  return (0, _editorCommands.stopPreservingSelection)({
81
83
  tr: newState.tr
@@ -121,7 +123,7 @@ var createSelectionPreservationPlugin = exports.createSelectionPreservationPlugi
121
123
  return false;
122
124
  },
123
125
  handleKeyDown: function handleKeyDown(view, event) {
124
- var _api$userIntent;
126
+ var _api$core, _api$blockControls;
125
127
  var _ref2 = _pluginKey.selectionPreservationPluginKey.getState(view.state) || {},
126
128
  preservedSelection = _ref2.preservedSelection;
127
129
 
@@ -129,26 +131,11 @@ var createSelectionPreservationPlugin = exports.createSelectionPreservationPlugi
129
131
  if (!preservedSelection) {
130
132
  return false;
131
133
  }
134
+ api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.commands) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.handleKeyDownWithPreservedSelection(event));
132
135
 
133
136
  // While preserving selection, if user presses delete/backspace, prevent event from being
134
- // handled by ProseMirror natively so that we can apply custom delete logic in block menu
135
- if (event.key === 'Backspace' || event.key === 'Delete') {
136
- return true;
137
- }
138
- var blockMenuOpen = (api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || (_api$userIntent = _api$userIntent.sharedState.currentState()) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.currentUserIntent) === 'blockMenuOpen';
139
-
140
- // When block menu is open, prevent all key events to avoid changing selection or editing content
141
- if (blockMenuOpen) {
142
- return true;
143
- }
144
-
145
- // When block menu isn't open and user presses any key, stop preserving selection
146
- var tr = view.state.tr;
147
- (0, _editorCommands.stopPreservingSelection)({
148
- tr: tr
149
- });
150
- view.dispatch(tr);
151
- return false;
137
+ // handled by ProseMirror natively so that we can apply logic using the preserved selection.
138
+ return ['backspace', 'delete'].includes(event.key.toLowerCase());
152
139
  }
153
140
  }
154
141
  });
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.hasUserSelectionChange = exports.getSelectionPreservationMeta = void 0;
6
+ exports.isSelectionWithinCodeBlock = exports.hasUserSelectionChange = exports.getSelectionPreservationMeta = void 0;
7
7
  var _pluginKey = require("./plugin-key");
8
8
  /**
9
9
  * Detects if any of the transactions include user-driven selection changes.
@@ -18,4 +18,16 @@ var hasUserSelectionChange = exports.hasUserSelectionChange = function hasUserSe
18
18
  };
19
19
  var getSelectionPreservationMeta = exports.getSelectionPreservationMeta = function getSelectionPreservationMeta(tr) {
20
20
  return tr.getMeta(_pluginKey.selectionPreservationPluginKey);
21
+ };
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';
21
33
  };
@@ -5,6 +5,7 @@ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
5
5
  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
+ import { handleKeyDownWithPreservedSelection } from './editor-commands/handle-key-down-with-preserved-selection';
8
9
  import { moveNode } from './editor-commands/move-node';
9
10
  import { moveNodeWithBlockMenu } from './editor-commands/move-node-with-block-menu';
10
11
  import { moveToLayout } from './editor-commands/move-to-layout';
@@ -92,9 +93,6 @@ export const blockControlsPlugin = ({
92
93
  tr
93
94
  });
94
95
  }
95
- stopPreservingSelection({
96
- tr
97
- });
98
96
  return tr;
99
97
  }
100
98
 
@@ -240,9 +238,8 @@ export const blockControlsPlugin = ({
240
238
  isSelectedViaDragHandle
241
239
  });
242
240
  },
243
- moveNodeWithBlockMenu: direction => {
244
- return moveNodeWithBlockMenu(api, direction);
245
- },
241
+ moveNodeWithBlockMenu: direction => moveNodeWithBlockMenu(api, direction),
242
+ handleKeyDownWithPreservedSelection: handleKeyDownWithPreservedSelection(api),
246
243
  startPreservingSelection: () => startPreservingSelection,
247
244
  stopPreservingSelection: () => stopPreservingSelection
248
245
  },
@@ -0,0 +1,79 @@
1
+ import { deleteSelectedRange } from '@atlaskit/editor-common/selection';
2
+ import { stopPreservingSelection } from '../pm-plugins/selection-preservation/editor-commands';
3
+ const getKeyboardEventInfo = event => {
4
+ const key = event.key.toLowerCase();
5
+ const isMetaCtrl = event.metaKey || event.ctrlKey;
6
+ const isDelete = ['backspace', 'delete'].includes(key);
7
+ const isCut = isMetaCtrl && key === 'x';
8
+ const isPaste = isMetaCtrl && key === 'v';
9
+ const isDestructive = isDelete || isCut || isPaste;
10
+ const isModifierOnly = ['control', 'meta', 'alt', 'shift'].includes(key) && !isMetaCtrl;
11
+ const isCopy = isMetaCtrl && key === 'c';
12
+ const isInert = isModifierOnly || isCopy;
13
+ return {
14
+ isDelete,
15
+ isDestructive,
16
+ isInert
17
+ };
18
+ };
19
+
20
+ /**
21
+ * Handles key presses when a selection is being preserved, when the block menu is open or closed.
22
+ *
23
+ * Based on the key pressed and whether the block menu is open or closed:
24
+ * 1. Handles key presses with custom logic (e.g. delete/backspace)
25
+ * 2. Closes the block menu
26
+ * 3. Stops preserving the selection
27
+ *
28
+ * This is used in two places:
29
+ * 1. selection preservation plugin when selection is being preserved, and focus is in the editor.
30
+ * 2. block menu UI component when focus is in the block menu.
31
+ *
32
+ * Ensures consistent behaviour for key presses in both scenarios.
33
+ */
34
+ export const handleKeyDownWithPreservedSelection = api => event => ({
35
+ tr
36
+ }) => {
37
+ var _api$userIntent, _api$userIntent$share;
38
+ if (!api) {
39
+ return tr;
40
+ }
41
+ const {
42
+ isDelete,
43
+ isDestructive,
44
+ isInert
45
+ } = getKeyboardEventInfo(event);
46
+
47
+ // Handle delete/backspace key presses with custom logic to ensure preserved selection is used
48
+ if (isDelete) {
49
+ var _api$blockControls, _api$blockControls$sh;
50
+ tr = deleteSelectedRange(tr, (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : (_api$blockControls$sh = _api$blockControls.sharedState.currentState()) === null || _api$blockControls$sh === void 0 ? void 0 : _api$blockControls$sh.preservedSelection);
51
+ }
52
+ const isBlockMenuOpen = ((_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 ? void 0 : (_api$userIntent$share = _api$userIntent.sharedState.currentState()) === null || _api$userIntent$share === void 0 ? void 0 : _api$userIntent$share.currentUserIntent) === 'blockMenuOpen';
53
+
54
+ // When selected content is being removed and the block menu is open
55
+ // close the block menu and refocus the editor
56
+ const shouldCloseBlockMenu = isDestructive && isBlockMenuOpen;
57
+ if (shouldCloseBlockMenu) {
58
+ var _api$blockControls2;
59
+ (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 ? void 0 : _api$blockControls2.commands.toggleBlockMenu({
60
+ closeMenu: true
61
+ })({
62
+ tr
63
+ });
64
+ api.core.actions.focus({
65
+ scrollIntoView: false
66
+ });
67
+ }
68
+
69
+ // Stop preserving when:
70
+ // 1. Content is being removed (delete/cut/paste) OR
71
+ // 2. Menu is closed AND user pressed a non-inert key (i.e. action which modifies selection or content)
72
+ const shouldStopPreservingSelection = isDestructive || !isBlockMenuOpen && !isInert;
73
+ if (shouldStopPreservingSelection) {
74
+ stopPreservingSelection({
75
+ tr
76
+ });
77
+ }
78
+ return tr;
79
+ };
@@ -4,8 +4,7 @@ import { DRAG_HANDLE_SELECTOR } from '@atlaskit/editor-common/styles';
4
4
  import { mapPreservedSelection } from '../utils/selection';
5
5
  import { stopPreservingSelection } from './editor-commands';
6
6
  import { selectionPreservationPluginKey } from './plugin-key';
7
- import { getSelectionPreservationMeta, hasUserSelectionChange } from './utils';
8
-
7
+ import { getSelectionPreservationMeta, hasUserSelectionChange, isSelectionWithinCodeBlock } from './utils';
9
8
  /**
10
9
  * Selection Preservation Plugin
11
10
  *
@@ -67,7 +66,9 @@ export const createSelectionPreservationPlugin = api => () => {
67
66
  if (!savedSel) {
68
67
  return null;
69
68
  }
70
- if (hasUserSelectionChange(transactions)) {
69
+
70
+ // Auto-stop if user explicitly changes selection or selection is set within a code block
71
+ if (hasUserSelectionChange(transactions) || isSelectionWithinCodeBlock(newState.selection)) {
71
72
  // Auto-stop if user explicitly changes selection
72
73
  return stopPreservingSelection({
73
74
  tr: newState.tr
@@ -114,7 +115,7 @@ export const createSelectionPreservationPlugin = api => () => {
114
115
  return false;
115
116
  },
116
117
  handleKeyDown: (view, event) => {
117
- var _api$userIntent, _api$userIntent$share;
118
+ var _api$core, _api$blockControls, _api$blockControls$co;
118
119
  const {
119
120
  preservedSelection
120
121
  } = selectionPreservationPluginKey.getState(view.state) || {};
@@ -123,26 +124,11 @@ export const createSelectionPreservationPlugin = api => () => {
123
124
  if (!preservedSelection) {
124
125
  return false;
125
126
  }
127
+ api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : (_api$blockControls$co = _api$blockControls.commands) === null || _api$blockControls$co === void 0 ? void 0 : _api$blockControls$co.handleKeyDownWithPreservedSelection(event));
126
128
 
127
129
  // While preserving selection, if user presses delete/backspace, prevent event from being
128
- // handled by ProseMirror natively so that we can apply custom delete logic in block menu
129
- if (event.key === 'Backspace' || event.key === 'Delete') {
130
- return true;
131
- }
132
- const blockMenuOpen = (api === null || api === void 0 ? void 0 : (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 ? void 0 : (_api$userIntent$share = _api$userIntent.sharedState.currentState()) === null || _api$userIntent$share === void 0 ? void 0 : _api$userIntent$share.currentUserIntent) === 'blockMenuOpen';
133
-
134
- // When block menu is open, prevent all key events to avoid changing selection or editing content
135
- if (blockMenuOpen) {
136
- return true;
137
- }
138
-
139
- // When block menu isn't open and user presses any key, stop preserving selection
140
- const tr = view.state.tr;
141
- stopPreservingSelection({
142
- tr
143
- });
144
- view.dispatch(tr);
145
- return false;
130
+ // handled by ProseMirror natively so that we can apply logic using the preserved selection.
131
+ return ['backspace', 'delete'].includes(event.key.toLowerCase());
146
132
  }
147
133
  }
148
134
  });
@@ -10,4 +10,17 @@ export const hasUserSelectionChange = transactions => {
10
10
  };
11
11
  export const getSelectionPreservationMeta = tr => {
12
12
  return tr.getMeta(selectionPreservationPluginKey);
13
+ };
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';
13
26
  };
@@ -8,6 +8,7 @@ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
8
8
  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
+ import { handleKeyDownWithPreservedSelection } from './editor-commands/handle-key-down-with-preserved-selection';
11
12
  import { moveNode } from './editor-commands/move-node';
12
13
  import { moveNodeWithBlockMenu as _moveNodeWithBlockMenu } from './editor-commands/move-node-with-block-menu';
13
14
  import { moveToLayout } from './editor-commands/move-to-layout';
@@ -95,9 +96,6 @@ export var blockControlsPlugin = function blockControlsPlugin(_ref) {
95
96
  tr: tr
96
97
  });
97
98
  }
98
- _stopPreservingSelection({
99
- tr: tr
100
- });
101
99
  return tr;
102
100
  }
103
101
 
@@ -242,6 +240,7 @@ export var blockControlsPlugin = function blockControlsPlugin(_ref) {
242
240
  moveNodeWithBlockMenu: function moveNodeWithBlockMenu(direction) {
243
241
  return _moveNodeWithBlockMenu(api, direction);
244
242
  },
243
+ handleKeyDownWithPreservedSelection: handleKeyDownWithPreservedSelection(api),
245
244
  startPreservingSelection: function startPreservingSelection() {
246
245
  return _startPreservingSelection;
247
246
  },
@@ -0,0 +1,81 @@
1
+ import { deleteSelectedRange } from '@atlaskit/editor-common/selection';
2
+ import { stopPreservingSelection } from '../pm-plugins/selection-preservation/editor-commands';
3
+ var getKeyboardEventInfo = function getKeyboardEventInfo(event) {
4
+ var key = event.key.toLowerCase();
5
+ var isMetaCtrl = event.metaKey || event.ctrlKey;
6
+ var isDelete = ['backspace', 'delete'].includes(key);
7
+ var isCut = isMetaCtrl && key === 'x';
8
+ var isPaste = isMetaCtrl && key === 'v';
9
+ var isDestructive = isDelete || isCut || isPaste;
10
+ var isModifierOnly = ['control', 'meta', 'alt', 'shift'].includes(key) && !isMetaCtrl;
11
+ var isCopy = isMetaCtrl && key === 'c';
12
+ var isInert = isModifierOnly || isCopy;
13
+ return {
14
+ isDelete: isDelete,
15
+ isDestructive: isDestructive,
16
+ isInert: isInert
17
+ };
18
+ };
19
+
20
+ /**
21
+ * Handles key presses when a selection is being preserved, when the block menu is open or closed.
22
+ *
23
+ * Based on the key pressed and whether the block menu is open or closed:
24
+ * 1. Handles key presses with custom logic (e.g. delete/backspace)
25
+ * 2. Closes the block menu
26
+ * 3. Stops preserving the selection
27
+ *
28
+ * This is used in two places:
29
+ * 1. selection preservation plugin when selection is being preserved, and focus is in the editor.
30
+ * 2. block menu UI component when focus is in the block menu.
31
+ *
32
+ * Ensures consistent behaviour for key presses in both scenarios.
33
+ */
34
+ export var handleKeyDownWithPreservedSelection = function handleKeyDownWithPreservedSelection(api) {
35
+ return function (event) {
36
+ return function (_ref) {
37
+ var _api$userIntent;
38
+ var tr = _ref.tr;
39
+ if (!api) {
40
+ return tr;
41
+ }
42
+ var _getKeyboardEventInfo = getKeyboardEventInfo(event),
43
+ isDelete = _getKeyboardEventInfo.isDelete,
44
+ isDestructive = _getKeyboardEventInfo.isDestructive,
45
+ isInert = _getKeyboardEventInfo.isInert;
46
+
47
+ // Handle delete/backspace key presses with custom logic to ensure preserved selection is used
48
+ if (isDelete) {
49
+ var _api$blockControls;
50
+ tr = deleteSelectedRange(tr, (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.sharedState.currentState()) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.preservedSelection);
51
+ }
52
+ var isBlockMenuOpen = ((_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || (_api$userIntent = _api$userIntent.sharedState.currentState()) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.currentUserIntent) === 'blockMenuOpen';
53
+
54
+ // When selected content is being removed and the block menu is open
55
+ // close the block menu and refocus the editor
56
+ var shouldCloseBlockMenu = isDestructive && isBlockMenuOpen;
57
+ if (shouldCloseBlockMenu) {
58
+ var _api$blockControls2;
59
+ (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || _api$blockControls2.commands.toggleBlockMenu({
60
+ closeMenu: true
61
+ })({
62
+ tr: tr
63
+ });
64
+ api.core.actions.focus({
65
+ scrollIntoView: false
66
+ });
67
+ }
68
+
69
+ // Stop preserving when:
70
+ // 1. Content is being removed (delete/cut/paste) OR
71
+ // 2. Menu is closed AND user pressed a non-inert key (i.e. action which modifies selection or content)
72
+ var shouldStopPreservingSelection = isDestructive || !isBlockMenuOpen && !isInert;
73
+ if (shouldStopPreservingSelection) {
74
+ stopPreservingSelection({
75
+ tr: tr
76
+ });
77
+ }
78
+ return tr;
79
+ };
80
+ };
81
+ };
@@ -7,8 +7,7 @@ import { DRAG_HANDLE_SELECTOR } from '@atlaskit/editor-common/styles';
7
7
  import { mapPreservedSelection } from '../utils/selection';
8
8
  import { stopPreservingSelection } from './editor-commands';
9
9
  import { selectionPreservationPluginKey } from './plugin-key';
10
- import { getSelectionPreservationMeta, hasUserSelectionChange } from './utils';
11
-
10
+ import { getSelectionPreservationMeta, hasUserSelectionChange, isSelectionWithinCodeBlock } from './utils';
12
11
  /**
13
12
  * Selection Preservation Plugin
14
13
  *
@@ -69,7 +68,9 @@ export var createSelectionPreservationPlugin = function createSelectionPreservat
69
68
  if (!savedSel) {
70
69
  return null;
71
70
  }
72
- if (hasUserSelectionChange(transactions)) {
71
+
72
+ // Auto-stop if user explicitly changes selection or selection is set within a code block
73
+ if (hasUserSelectionChange(transactions) || isSelectionWithinCodeBlock(newState.selection)) {
73
74
  // Auto-stop if user explicitly changes selection
74
75
  return stopPreservingSelection({
75
76
  tr: newState.tr
@@ -115,7 +116,7 @@ export var createSelectionPreservationPlugin = function createSelectionPreservat
115
116
  return false;
116
117
  },
117
118
  handleKeyDown: function handleKeyDown(view, event) {
118
- var _api$userIntent;
119
+ var _api$core, _api$blockControls;
119
120
  var _ref2 = selectionPreservationPluginKey.getState(view.state) || {},
120
121
  preservedSelection = _ref2.preservedSelection;
121
122
 
@@ -123,26 +124,11 @@ export var createSelectionPreservationPlugin = function createSelectionPreservat
123
124
  if (!preservedSelection) {
124
125
  return false;
125
126
  }
127
+ api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.commands) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.handleKeyDownWithPreservedSelection(event));
126
128
 
127
129
  // While preserving selection, if user presses delete/backspace, prevent event from being
128
- // handled by ProseMirror natively so that we can apply custom delete logic in block menu
129
- if (event.key === 'Backspace' || event.key === 'Delete') {
130
- return true;
131
- }
132
- var blockMenuOpen = (api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || (_api$userIntent = _api$userIntent.sharedState.currentState()) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.currentUserIntent) === 'blockMenuOpen';
133
-
134
- // When block menu is open, prevent all key events to avoid changing selection or editing content
135
- if (blockMenuOpen) {
136
- return true;
137
- }
138
-
139
- // When block menu isn't open and user presses any key, stop preserving selection
140
- var tr = view.state.tr;
141
- stopPreservingSelection({
142
- tr: tr
143
- });
144
- view.dispatch(tr);
145
- return false;
130
+ // handled by ProseMirror natively so that we can apply logic using the preserved selection.
131
+ return ['backspace', 'delete'].includes(event.key.toLowerCase());
146
132
  }
147
133
  }
148
134
  });
@@ -12,4 +12,16 @@ export var hasUserSelectionChange = function hasUserSelectionChange(transactions
12
12
  };
13
13
  export var getSelectionPreservationMeta = function getSelectionPreservationMeta(tr) {
14
14
  return tr.getMeta(selectionPreservationPluginKey);
15
+ };
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';
15
27
  };
@@ -1,8 +1,8 @@
1
- import { type IntlShape } from 'react-intl-next';
2
- import { type INPUT_METHOD } from '@atlaskit/editor-common/analytics';
1
+ import type { IntlShape } from 'react-intl-next';
2
+ import type { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
3
3
  import type { DIRECTION, EditorCommand, NextEditorPlugin, OptionalPlugin } from '@atlaskit/editor-common/types';
4
4
  import type { AccessibilityUtilsPlugin } from '@atlaskit/editor-plugin-accessibility-utils';
5
- import { type AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
5
+ import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
6
6
  import type { EditorDisabledPlugin } from '@atlaskit/editor-plugin-editor-disabled';
7
7
  import type { FeatureFlagsPlugin } from '@atlaskit/editor-plugin-feature-flags';
8
8
  import type { InteractionPlugin } from '@atlaskit/editor-plugin-interaction';
@@ -15,8 +15,8 @@ import type { TypeAheadPlugin } from '@atlaskit/editor-plugin-type-ahead';
15
15
  import type { UserIntentPlugin } from '@atlaskit/editor-plugin-user-intent';
16
16
  import type { WidthPlugin } from '@atlaskit/editor-plugin-width';
17
17
  import type { Selection } from '@atlaskit/editor-prosemirror/state';
18
- import { type Mapping } from '@atlaskit/editor-prosemirror/transform';
19
- import { type DecorationSet } from '@atlaskit/editor-prosemirror/view';
18
+ import type { Mapping } from '@atlaskit/editor-prosemirror/transform';
19
+ import type { DecorationSet } from '@atlaskit/editor-prosemirror/view';
20
20
  export type ActiveNode = {
21
21
  anchorName: string;
22
22
  handleOptions?: HandleOptions;
@@ -118,6 +118,7 @@ export type BlockControlsPluginDependencies = [
118
118
  ];
119
119
  export type BlockControlsPlugin = NextEditorPlugin<'blockControls', {
120
120
  commands: {
121
+ handleKeyDownWithPreservedSelection: (event: KeyboardEvent) => EditorCommand;
121
122
  moveNode: MoveNode;
122
123
  moveNodeWithBlockMenu: (direction: DIRECTION.UP | DIRECTION.DOWN) => EditorCommand;
123
124
  /**
@@ -0,0 +1,17 @@
1
+ import type { EditorCommand, ExtractInjectionAPI } from '@atlaskit/editor-common/types';
2
+ import type { BlockControlsPlugin } from '../blockControlsPluginType';
3
+ /**
4
+ * Handles key presses when a selection is being preserved, when the block menu is open or closed.
5
+ *
6
+ * Based on the key pressed and whether the block menu is open or closed:
7
+ * 1. Handles key presses with custom logic (e.g. delete/backspace)
8
+ * 2. Closes the block menu
9
+ * 3. Stops preserving the selection
10
+ *
11
+ * This is used in two places:
12
+ * 1. selection preservation plugin when selection is being preserved, and focus is in the editor.
13
+ * 2. block menu UI component when focus is in the block menu.
14
+ *
15
+ * Ensures consistent behaviour for key presses in both scenarios.
16
+ */
17
+ export declare const handleKeyDownWithPreservedSelection: (api?: ExtractInjectionAPI<BlockControlsPlugin>) => (event: KeyboardEvent) => EditorCommand;
@@ -32,4 +32,4 @@ import type { SelectionPreservationPluginState } from './types';
32
32
  *
33
33
  * https://hello.atlassian.net/wiki/spaces/egcuc/pages/6170822503/Block+Menu+Solution+for+multi-select+and+selection+preservation
34
34
  */
35
- export declare const createSelectionPreservationPlugin: (api: ExtractInjectionAPI<BlockControlsPlugin> | undefined) => () => SafePlugin<SelectionPreservationPluginState>;
35
+ export declare const createSelectionPreservationPlugin: (api?: ExtractInjectionAPI<BlockControlsPlugin>) => () => SafePlugin<SelectionPreservationPluginState>;
@@ -1,4 +1,4 @@
1
- import type { ReadonlyTransaction, 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.
@@ -8,3 +8,10 @@ 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;
@@ -1,8 +1,8 @@
1
- import { type IntlShape } from 'react-intl-next';
2
- import { type INPUT_METHOD } from '@atlaskit/editor-common/analytics';
1
+ import type { IntlShape } from 'react-intl-next';
2
+ import type { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
3
3
  import type { DIRECTION, EditorCommand, NextEditorPlugin, OptionalPlugin } from '@atlaskit/editor-common/types';
4
4
  import type { AccessibilityUtilsPlugin } from '@atlaskit/editor-plugin-accessibility-utils';
5
- import { type AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
5
+ import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
6
6
  import type { EditorDisabledPlugin } from '@atlaskit/editor-plugin-editor-disabled';
7
7
  import type { FeatureFlagsPlugin } from '@atlaskit/editor-plugin-feature-flags';
8
8
  import type { InteractionPlugin } from '@atlaskit/editor-plugin-interaction';
@@ -15,8 +15,8 @@ import type { TypeAheadPlugin } from '@atlaskit/editor-plugin-type-ahead';
15
15
  import type { UserIntentPlugin } from '@atlaskit/editor-plugin-user-intent';
16
16
  import type { WidthPlugin } from '@atlaskit/editor-plugin-width';
17
17
  import type { Selection } from '@atlaskit/editor-prosemirror/state';
18
- import { type Mapping } from '@atlaskit/editor-prosemirror/transform';
19
- import { type DecorationSet } from '@atlaskit/editor-prosemirror/view';
18
+ import type { Mapping } from '@atlaskit/editor-prosemirror/transform';
19
+ import type { DecorationSet } from '@atlaskit/editor-prosemirror/view';
20
20
  export type ActiveNode = {
21
21
  anchorName: string;
22
22
  handleOptions?: HandleOptions;
@@ -118,6 +118,7 @@ export type BlockControlsPluginDependencies = [
118
118
  ];
119
119
  export type BlockControlsPlugin = NextEditorPlugin<'blockControls', {
120
120
  commands: {
121
+ handleKeyDownWithPreservedSelection: (event: KeyboardEvent) => EditorCommand;
121
122
  moveNode: MoveNode;
122
123
  moveNodeWithBlockMenu: (direction: DIRECTION.UP | DIRECTION.DOWN) => EditorCommand;
123
124
  /**
@@ -0,0 +1,17 @@
1
+ import type { EditorCommand, ExtractInjectionAPI } from '@atlaskit/editor-common/types';
2
+ import type { BlockControlsPlugin } from '../blockControlsPluginType';
3
+ /**
4
+ * Handles key presses when a selection is being preserved, when the block menu is open or closed.
5
+ *
6
+ * Based on the key pressed and whether the block menu is open or closed:
7
+ * 1. Handles key presses with custom logic (e.g. delete/backspace)
8
+ * 2. Closes the block menu
9
+ * 3. Stops preserving the selection
10
+ *
11
+ * This is used in two places:
12
+ * 1. selection preservation plugin when selection is being preserved, and focus is in the editor.
13
+ * 2. block menu UI component when focus is in the block menu.
14
+ *
15
+ * Ensures consistent behaviour for key presses in both scenarios.
16
+ */
17
+ export declare const handleKeyDownWithPreservedSelection: (api?: ExtractInjectionAPI<BlockControlsPlugin>) => (event: KeyboardEvent) => EditorCommand;
@@ -32,4 +32,4 @@ import type { SelectionPreservationPluginState } from './types';
32
32
  *
33
33
  * https://hello.atlassian.net/wiki/spaces/egcuc/pages/6170822503/Block+Menu+Solution+for+multi-select+and+selection+preservation
34
34
  */
35
- export declare const createSelectionPreservationPlugin: (api: ExtractInjectionAPI<BlockControlsPlugin> | undefined) => () => SafePlugin<SelectionPreservationPluginState>;
35
+ export declare const createSelectionPreservationPlugin: (api?: ExtractInjectionAPI<BlockControlsPlugin>) => () => SafePlugin<SelectionPreservationPluginState>;
@@ -1,4 +1,4 @@
1
- import type { ReadonlyTransaction, 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.
@@ -8,3 +8,10 @@ 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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-block-controls",
3
- "version": "7.19.0",
3
+ "version": "8.0.0",
4
4
  "description": "Block controls plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -30,19 +30,19 @@
30
30
  "dependencies": {
31
31
  "@atlaskit/adf-schema": "^51.5.0",
32
32
  "@atlaskit/browser-apis": "^0.0.1",
33
- "@atlaskit/editor-plugin-accessibility-utils": "^6.0.0",
34
- "@atlaskit/editor-plugin-analytics": "^6.2.0",
35
- "@atlaskit/editor-plugin-editor-disabled": "^6.1.0",
36
- "@atlaskit/editor-plugin-feature-flags": "^5.0.0",
37
- "@atlaskit/editor-plugin-interaction": "^11.0.0",
38
- "@atlaskit/editor-plugin-limited-mode": "^3.2.0",
39
- "@atlaskit/editor-plugin-metrics": "^7.1.0",
40
- "@atlaskit/editor-plugin-quick-insert": "^6.2.0",
41
- "@atlaskit/editor-plugin-selection": "^6.1.0",
42
- "@atlaskit/editor-plugin-toolbar": "^3.5.0",
43
- "@atlaskit/editor-plugin-type-ahead": "^6.6.0",
44
- "@atlaskit/editor-plugin-user-intent": "^4.0.0",
45
- "@atlaskit/editor-plugin-width": "^7.0.0",
33
+ "@atlaskit/editor-plugin-accessibility-utils": "^7.0.0",
34
+ "@atlaskit/editor-plugin-analytics": "^7.0.0",
35
+ "@atlaskit/editor-plugin-editor-disabled": "^7.0.0",
36
+ "@atlaskit/editor-plugin-feature-flags": "^6.0.0",
37
+ "@atlaskit/editor-plugin-interaction": "^12.0.0",
38
+ "@atlaskit/editor-plugin-limited-mode": "^4.0.0",
39
+ "@atlaskit/editor-plugin-metrics": "^8.0.0",
40
+ "@atlaskit/editor-plugin-quick-insert": "^7.0.0",
41
+ "@atlaskit/editor-plugin-selection": "^7.0.0",
42
+ "@atlaskit/editor-plugin-toolbar": "^4.0.0",
43
+ "@atlaskit/editor-plugin-type-ahead": "^7.0.0",
44
+ "@atlaskit/editor-plugin-user-intent": "^5.0.0",
45
+ "@atlaskit/editor-plugin-width": "^8.0.0",
46
46
  "@atlaskit/editor-prosemirror": "^7.2.0",
47
47
  "@atlaskit/editor-shared-styles": "^3.10.0",
48
48
  "@atlaskit/editor-tables": "^2.9.0",
@@ -66,7 +66,7 @@
66
66
  "uuid": "^3.1.0"
67
67
  },
68
68
  "peerDependencies": {
69
- "@atlaskit/editor-common": "^110.50.0",
69
+ "@atlaskit/editor-common": "^111.0.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"