@atlaskit/editor-plugin-block-controls 7.18.3 → 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.
Files changed (25) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/blockControlsPlugin.js +2 -3
  3. package/dist/cjs/editor-commands/handle-key-down-with-preserved-selection.js +87 -0
  4. package/dist/cjs/pm-plugins/decorations-drag-handle.js +5 -50
  5. package/dist/cjs/pm-plugins/selection-preservation/pm-plugin.js +7 -20
  6. package/dist/cjs/pm-plugins/selection-preservation/utils.js +13 -1
  7. package/dist/es2019/blockControlsPlugin.js +3 -6
  8. package/dist/es2019/editor-commands/handle-key-down-with-preserved-selection.js +79 -0
  9. package/dist/es2019/pm-plugins/decorations-drag-handle.js +3 -41
  10. package/dist/es2019/pm-plugins/selection-preservation/pm-plugin.js +8 -22
  11. package/dist/es2019/pm-plugins/selection-preservation/utils.js +13 -0
  12. package/dist/esm/blockControlsPlugin.js +2 -3
  13. package/dist/esm/editor-commands/handle-key-down-with-preserved-selection.js +81 -0
  14. package/dist/esm/pm-plugins/decorations-drag-handle.js +5 -50
  15. package/dist/esm/pm-plugins/selection-preservation/pm-plugin.js +8 -22
  16. package/dist/esm/pm-plugins/selection-preservation/utils.js +12 -0
  17. package/dist/types/blockControlsPluginType.d.ts +6 -5
  18. package/dist/types/editor-commands/handle-key-down-with-preserved-selection.d.ts +17 -0
  19. package/dist/types/pm-plugins/selection-preservation/pm-plugin.d.ts +1 -1
  20. package/dist/types/pm-plugins/selection-preservation/utils.d.ts +8 -1
  21. package/dist/types-ts4.5/blockControlsPluginType.d.ts +6 -5
  22. package/dist/types-ts4.5/editor-commands/handle-key-down-with-preserved-selection.d.ts +17 -0
  23. package/dist/types-ts4.5/pm-plugins/selection-preservation/pm-plugin.d.ts +1 -1
  24. package/dist/types-ts4.5/pm-plugins/selection-preservation/utils.d.ts +8 -1
  25. package/package.json +15 -15
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
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
+
11
+ ## 7.19.0
12
+
13
+ ### Minor Changes
14
+
15
+ - [`cae218eb0956b`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/cae218eb0956b) -
16
+ ED-29725 fix table controls when native anchor enabled
17
+
3
18
  ## 7.18.3
4
19
 
5
20
  ### Patch 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
+ };
@@ -33,50 +33,6 @@ var findHandleDec = exports.findHandleDec = function findHandleDec(decorations,
33
33
  return spec.type === _decorationsCommon.TYPE_HANDLE_DEC;
34
34
  });
35
35
  };
36
- /**
37
- * Fix for widget positioning to ensure it is not placed into a previous mark's DOM structure.
38
- * A ProseMirror widget can appear in the wrong DOM position, specifically being added
39
- * to a previous mark instead of its intended location, which leads to various rendering issues.
40
- * For example, when nodeBefore has an alignment mark but the current node doesn't, ProseMirror may
41
- * incorrectly render the widget inside the previous node's alignment mark wrapper instead.
42
- */
43
- var fixWidgetSide = function fixWidgetSide($pos) {
44
- var defaultSide = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
45
- var alignmentMark = $pos.doc.type.schema.marks.alignment;
46
- var indentationMark = $pos.doc.type.schema.marks.indentation;
47
-
48
- // Only apply fix for alignment and indent marks
49
- if (!alignmentMark && !indentationMark) {
50
- return defaultSide;
51
- }
52
- if ($pos.nodeBefore && $pos.nodeAfter) {
53
- var _$pos$nodeBefore, _$pos$nodeAfter;
54
- var beforeMarks = ((_$pos$nodeBefore = $pos.nodeBefore) === null || _$pos$nodeBefore === void 0 ? void 0 : _$pos$nodeBefore.marks.filter(function (mark) {
55
- return mark.type === alignmentMark || mark.type === indentationMark;
56
- })) || [];
57
- var afterMarks = ((_$pos$nodeAfter = $pos.nodeAfter) === null || _$pos$nodeAfter === void 0 ? void 0 : _$pos$nodeAfter.marks.filter(function (mark) {
58
- return mark.type === alignmentMark || mark.type === indentationMark;
59
- })) || [];
60
- if (beforeMarks.length === 0) {
61
- return defaultSide;
62
- } else if (afterMarks.length === 0) {
63
- return 0;
64
- }
65
-
66
- // Check if previous node has marks that current node doesn't have
67
- var hasMissingMark = beforeMarks.some(function (mark) {
68
- return !afterMarks.some(function (nextMark) {
69
- return nextMark.eq(mark);
70
- });
71
- });
72
-
73
- // if we have missing mark, we set side to 0 to render widget outside previous mark DOM
74
- if (hasMissingMark) {
75
- return 0;
76
- }
77
- }
78
- return defaultSide;
79
- };
80
36
  var dragHandleDecoration = exports.dragHandleDecoration = function dragHandleDecoration(_ref) {
81
37
  var api = _ref.api,
82
38
  formatMessage = _ref.formatMessage,
@@ -95,10 +51,8 @@ var dragHandleDecoration = exports.dragHandleDecoration = function dragHandleDec
95
51
  var unbind;
96
52
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
97
53
  var key = (0, _uuid.default)();
98
- var $pos = editorState.doc.resolve(pos);
99
- var side = (0, _expValEquals.expValEquals)('platform_editor_native_anchor_with_dnd', 'isEnabled', true) && (0, _platformFeatureFlags.fg)('platform_editor_native_anchor_patch_1') ? fixWidgetSide($pos) : -1;
100
54
  var widgetSpec = (0, _experiments.editorExperiment)('platform_editor_breakout_resizing', true) ? {
101
- side: side,
55
+ side: -1,
102
56
  type: _decorationsCommon.TYPE_HANDLE_DEC,
103
57
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
104
58
  testid: "".concat(_decorationsCommon.TYPE_HANDLE_DEC, "-").concat((0, _uuid.default)()),
@@ -115,10 +69,11 @@ var dragHandleDecoration = exports.dragHandleDecoration = function dragHandleDec
115
69
  }
116
70
  }
117
71
  } : {
118
- side: side,
72
+ side: -1,
119
73
  type: _decorationsCommon.TYPE_HANDLE_DEC,
120
74
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
121
75
  testid: "".concat(_decorationsCommon.TYPE_HANDLE_DEC, "-").concat((0, _uuid.default)()),
76
+ marks: (0, _expValEquals.expValEquals)('platform_editor_native_anchor_with_dnd', 'isEnabled', true) && (0, _platformFeatureFlags.fg)('platform_editor_native_anchor_patch_1') ? (0, _marks.getActiveBlockMarks)(editorState, pos) : undefined,
122
77
  destroy: function destroy(node) {
123
78
  unbind && unbind();
124
79
  if ((0, _experiments.editorExperiment)('platform_editor_block_control_optimise_render', true) && node instanceof HTMLElement) {
@@ -144,8 +99,8 @@ var dragHandleDecoration = exports.dragHandleDecoration = function dragHandleDec
144
99
  };
145
100
  var newPos = getPos();
146
101
  if (typeof newPos === 'number') {
147
- var _$pos = view.state.doc.resolve(newPos);
148
- isTopLevelNode = (_$pos === null || _$pos === void 0 ? void 0 : _$pos.parent.type.name) === 'doc';
102
+ var $pos = view.state.doc.resolve(newPos);
103
+ isTopLevelNode = ($pos === null || $pos === void 0 ? void 0 : $pos.parent.type.name) === 'doc';
149
104
  }
150
105
  /*
151
106
  * We disable mouseover event to fix flickering issue on hover
@@ -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
+ };
@@ -23,43 +23,6 @@ export const emptyParagraphNodeDecorations = () => {
23
23
  export const findHandleDec = (decorations, from, to) => {
24
24
  return decorations.find(from, to, spec => spec.type === TYPE_HANDLE_DEC);
25
25
  };
26
- /**
27
- * Fix for widget positioning to ensure it is not placed into a previous mark's DOM structure.
28
- * A ProseMirror widget can appear in the wrong DOM position, specifically being added
29
- * to a previous mark instead of its intended location, which leads to various rendering issues.
30
- * For example, when nodeBefore has an alignment mark but the current node doesn't, ProseMirror may
31
- * incorrectly render the widget inside the previous node's alignment mark wrapper instead.
32
- */
33
- const fixWidgetSide = ($pos, defaultSide = -1) => {
34
- const alignmentMark = $pos.doc.type.schema.marks.alignment;
35
- const indentationMark = $pos.doc.type.schema.marks.indentation;
36
-
37
- // Only apply fix for alignment and indent marks
38
- if (!alignmentMark && !indentationMark) {
39
- return defaultSide;
40
- }
41
- if ($pos.nodeBefore && $pos.nodeAfter) {
42
- var _$pos$nodeBefore, _$pos$nodeAfter;
43
- const beforeMarks = ((_$pos$nodeBefore = $pos.nodeBefore) === null || _$pos$nodeBefore === void 0 ? void 0 : _$pos$nodeBefore.marks.filter(mark => mark.type === alignmentMark || mark.type === indentationMark)) || [];
44
- const afterMarks = ((_$pos$nodeAfter = $pos.nodeAfter) === null || _$pos$nodeAfter === void 0 ? void 0 : _$pos$nodeAfter.marks.filter(mark => mark.type === alignmentMark || mark.type === indentationMark)) || [];
45
- if (beforeMarks.length === 0) {
46
- return defaultSide;
47
- } else if (afterMarks.length === 0) {
48
- return 0;
49
- }
50
-
51
- // Check if previous node has marks that current node doesn't have
52
- const hasMissingMark = beforeMarks.some(mark => {
53
- return !afterMarks.some(nextMark => nextMark.eq(mark));
54
- });
55
-
56
- // if we have missing mark, we set side to 0 to render widget outside previous mark DOM
57
- if (hasMissingMark) {
58
- return 0;
59
- }
60
- }
61
- return defaultSide;
62
- };
63
26
  export const dragHandleDecoration = ({
64
27
  api,
65
28
  formatMessage,
@@ -79,10 +42,8 @@ export const dragHandleDecoration = ({
79
42
  let unbind;
80
43
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
81
44
  const key = uuid();
82
- const $pos = editorState.doc.resolve(pos);
83
- const side = expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) && fg('platform_editor_native_anchor_patch_1') ? fixWidgetSide($pos) : -1;
84
45
  const widgetSpec = editorExperiment('platform_editor_breakout_resizing', true) ? {
85
- side,
46
+ side: -1,
86
47
  type: TYPE_HANDLE_DEC,
87
48
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
88
49
  testid: `${TYPE_HANDLE_DEC}-${uuid()}`,
@@ -99,10 +60,11 @@ export const dragHandleDecoration = ({
99
60
  }
100
61
  }
101
62
  } : {
102
- side,
63
+ side: -1,
103
64
  type: TYPE_HANDLE_DEC,
104
65
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
105
66
  testid: `${TYPE_HANDLE_DEC}-${uuid()}`,
67
+ marks: expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) && fg('platform_editor_native_anchor_patch_1') ? getActiveBlockMarks(editorState, pos) : undefined,
106
68
  destroy: node => {
107
69
  unbind && unbind();
108
70
  if (editorExperiment('platform_editor_block_control_optimise_render', true) && node instanceof HTMLElement) {
@@ -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
+ };
@@ -25,50 +25,6 @@ export var findHandleDec = function findHandleDec(decorations, from, to) {
25
25
  return spec.type === TYPE_HANDLE_DEC;
26
26
  });
27
27
  };
28
- /**
29
- * Fix for widget positioning to ensure it is not placed into a previous mark's DOM structure.
30
- * A ProseMirror widget can appear in the wrong DOM position, specifically being added
31
- * to a previous mark instead of its intended location, which leads to various rendering issues.
32
- * For example, when nodeBefore has an alignment mark but the current node doesn't, ProseMirror may
33
- * incorrectly render the widget inside the previous node's alignment mark wrapper instead.
34
- */
35
- var fixWidgetSide = function fixWidgetSide($pos) {
36
- var defaultSide = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
37
- var alignmentMark = $pos.doc.type.schema.marks.alignment;
38
- var indentationMark = $pos.doc.type.schema.marks.indentation;
39
-
40
- // Only apply fix for alignment and indent marks
41
- if (!alignmentMark && !indentationMark) {
42
- return defaultSide;
43
- }
44
- if ($pos.nodeBefore && $pos.nodeAfter) {
45
- var _$pos$nodeBefore, _$pos$nodeAfter;
46
- var beforeMarks = ((_$pos$nodeBefore = $pos.nodeBefore) === null || _$pos$nodeBefore === void 0 ? void 0 : _$pos$nodeBefore.marks.filter(function (mark) {
47
- return mark.type === alignmentMark || mark.type === indentationMark;
48
- })) || [];
49
- var afterMarks = ((_$pos$nodeAfter = $pos.nodeAfter) === null || _$pos$nodeAfter === void 0 ? void 0 : _$pos$nodeAfter.marks.filter(function (mark) {
50
- return mark.type === alignmentMark || mark.type === indentationMark;
51
- })) || [];
52
- if (beforeMarks.length === 0) {
53
- return defaultSide;
54
- } else if (afterMarks.length === 0) {
55
- return 0;
56
- }
57
-
58
- // Check if previous node has marks that current node doesn't have
59
- var hasMissingMark = beforeMarks.some(function (mark) {
60
- return !afterMarks.some(function (nextMark) {
61
- return nextMark.eq(mark);
62
- });
63
- });
64
-
65
- // if we have missing mark, we set side to 0 to render widget outside previous mark DOM
66
- if (hasMissingMark) {
67
- return 0;
68
- }
69
- }
70
- return defaultSide;
71
- };
72
28
  export var dragHandleDecoration = function dragHandleDecoration(_ref) {
73
29
  var api = _ref.api,
74
30
  formatMessage = _ref.formatMessage,
@@ -87,10 +43,8 @@ export var dragHandleDecoration = function dragHandleDecoration(_ref) {
87
43
  var unbind;
88
44
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
89
45
  var key = uuid();
90
- var $pos = editorState.doc.resolve(pos);
91
- var side = expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) && fg('platform_editor_native_anchor_patch_1') ? fixWidgetSide($pos) : -1;
92
46
  var widgetSpec = editorExperiment('platform_editor_breakout_resizing', true) ? {
93
- side: side,
47
+ side: -1,
94
48
  type: TYPE_HANDLE_DEC,
95
49
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
96
50
  testid: "".concat(TYPE_HANDLE_DEC, "-").concat(uuid()),
@@ -107,10 +61,11 @@ export var dragHandleDecoration = function dragHandleDecoration(_ref) {
107
61
  }
108
62
  }
109
63
  } : {
110
- side: side,
64
+ side: -1,
111
65
  type: TYPE_HANDLE_DEC,
112
66
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
113
67
  testid: "".concat(TYPE_HANDLE_DEC, "-").concat(uuid()),
68
+ marks: expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) && fg('platform_editor_native_anchor_patch_1') ? getActiveBlockMarks(editorState, pos) : undefined,
114
69
  destroy: function destroy(node) {
115
70
  unbind && unbind();
116
71
  if (editorExperiment('platform_editor_block_control_optimise_render', true) && node instanceof HTMLElement) {
@@ -136,8 +91,8 @@ export var dragHandleDecoration = function dragHandleDecoration(_ref) {
136
91
  };
137
92
  var newPos = getPos();
138
93
  if (typeof newPos === 'number') {
139
- var _$pos = view.state.doc.resolve(newPos);
140
- isTopLevelNode = (_$pos === null || _$pos === void 0 ? void 0 : _$pos.parent.type.name) === 'doc';
94
+ var $pos = view.state.doc.resolve(newPos);
95
+ isTopLevelNode = ($pos === null || $pos === void 0 ? void 0 : $pos.parent.type.name) === 'doc';
141
96
  }
142
97
  /*
143
98
  * We disable mouseover event to fix flickering issue on hover
@@ -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.18.3",
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.49.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"