@atlaskit/editor-plugin-block-controls 7.7.1 → 7.7.3

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 (38) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/blockControlsPlugin.js +14 -0
  3. package/dist/cjs/pm-plugins/main.js +10 -4
  4. package/dist/cjs/pm-plugins/selection-preservation/editor-commands.js +32 -0
  5. package/dist/cjs/pm-plugins/selection-preservation/plugin-key.js +8 -0
  6. package/dist/cjs/pm-plugins/selection-preservation/pm-plugin.js +99 -0
  7. package/dist/cjs/pm-plugins/selection-preservation/types.js +5 -0
  8. package/dist/cjs/pm-plugins/selection-preservation/utils.js +24 -0
  9. package/dist/cjs/ui/drag-handle.js +196 -61
  10. package/dist/es2019/blockControlsPlugin.js +11 -1
  11. package/dist/es2019/pm-plugins/main.js +10 -4
  12. package/dist/es2019/pm-plugins/selection-preservation/editor-commands.js +28 -0
  13. package/dist/es2019/pm-plugins/selection-preservation/plugin-key.js +2 -0
  14. package/dist/es2019/pm-plugins/selection-preservation/pm-plugin.js +92 -0
  15. package/dist/es2019/pm-plugins/selection-preservation/types.js +1 -0
  16. package/dist/es2019/pm-plugins/selection-preservation/utils.js +16 -0
  17. package/dist/es2019/ui/drag-handle.js +170 -31
  18. package/dist/esm/blockControlsPlugin.js +14 -0
  19. package/dist/esm/pm-plugins/main.js +10 -4
  20. package/dist/esm/pm-plugins/selection-preservation/editor-commands.js +26 -0
  21. package/dist/esm/pm-plugins/selection-preservation/plugin-key.js +2 -0
  22. package/dist/esm/pm-plugins/selection-preservation/pm-plugin.js +93 -0
  23. package/dist/esm/pm-plugins/selection-preservation/types.js +1 -0
  24. package/dist/esm/pm-plugins/selection-preservation/utils.js +18 -0
  25. package/dist/esm/ui/drag-handle.js +195 -60
  26. package/dist/types/blockControlsPluginType.d.ts +12 -1
  27. package/dist/types/pm-plugins/selection-preservation/editor-commands.d.ts +13 -0
  28. package/dist/types/pm-plugins/selection-preservation/plugin-key.d.ts +3 -0
  29. package/dist/types/pm-plugins/selection-preservation/pm-plugin.d.ts +26 -0
  30. package/dist/types/pm-plugins/selection-preservation/types.d.ts +7 -0
  31. package/dist/types/pm-plugins/selection-preservation/utils.d.ts +10 -0
  32. package/dist/types-ts4.5/blockControlsPluginType.d.ts +12 -1
  33. package/dist/types-ts4.5/pm-plugins/selection-preservation/editor-commands.d.ts +13 -0
  34. package/dist/types-ts4.5/pm-plugins/selection-preservation/plugin-key.d.ts +3 -0
  35. package/dist/types-ts4.5/pm-plugins/selection-preservation/pm-plugin.d.ts +26 -0
  36. package/dist/types-ts4.5/pm-plugins/selection-preservation/types.d.ts +7 -0
  37. package/dist/types-ts4.5/pm-plugins/selection-preservation/utils.d.ts +10 -0
  38. package/package.json +7 -6
@@ -11,11 +11,13 @@ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/de
11
11
  var _react = require("react");
12
12
  var _react2 = require("@emotion/react");
13
13
  var _bindEventListener = require("bind-event-listener");
14
+ var _browserApis = require("@atlaskit/browser-apis");
14
15
  var _analytics = require("@atlaskit/editor-common/analytics");
15
16
  var _browser = require("@atlaskit/editor-common/browser");
16
17
  var _hooks = require("@atlaskit/editor-common/hooks");
17
18
  var _keymaps = require("@atlaskit/editor-common/keymaps");
18
19
  var _messages = require("@atlaskit/editor-common/messages");
20
+ var _selection2 = require("@atlaskit/editor-common/selection");
19
21
  var _styles = require("@atlaskit/editor-common/styles");
20
22
  var _useSharedPluginStateSelector = require("@atlaskit/editor-common/use-shared-plugin-state-selector");
21
23
  var _state = require("@atlaskit/editor-prosemirror/state");
@@ -33,10 +35,11 @@ var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
33
35
  var _tooltip = _interopRequireDefault(require("@atlaskit/tooltip"));
34
36
  var _decorationsCommon = require("../pm-plugins/decorations-common");
35
37
  var _main = require("../pm-plugins/main");
38
+ var _pluginKey = require("../pm-plugins/selection-preservation/plugin-key");
36
39
  var _analytics2 = require("../pm-plugins/utils/analytics");
37
40
  var _dragHandlePositions = require("../pm-plugins/utils/drag-handle-positions");
38
41
  var _getSelection = require("../pm-plugins/utils/getSelection");
39
- var _selection2 = require("../pm-plugins/utils/selection");
42
+ var _selection3 = require("../pm-plugins/utils/selection");
40
43
  var _consts2 = require("./consts");
41
44
  var _dragPreview = require("./drag-preview");
42
45
  var _anchorName = require("./utils/anchor-name");
@@ -295,18 +298,81 @@ var getNodeMargins = function getNodeMargins(node) {
295
298
  }
296
299
  return _consts2.nodeMargins[nodeTypeName] || _consts2.nodeMargins['default'];
297
300
  };
298
- var DragHandle = exports.DragHandle = function DragHandle(_ref) {
299
- var _api$core3;
300
- var view = _ref.view,
301
- api = _ref.api,
302
- formatMessage = _ref.formatMessage,
303
- getPos = _ref.getPos,
304
- anchorName = _ref.anchorName,
305
- nodeType = _ref.nodeType,
306
- handleOptions = _ref.handleOptions,
307
- _ref$isTopLevelNode = _ref.isTopLevelNode,
308
- isTopLevelNode = _ref$isTopLevelNode === void 0 ? true : _ref$isTopLevelNode,
309
- anchorRectCache = _ref.anchorRectCache;
301
+ var isRangeSpanningMultipleNodes = function isRangeSpanningMultipleNodes(range) {
302
+ if (range.endIndex - range.startIndex <= 1) {
303
+ return false; // At most one child
304
+ }
305
+
306
+ // Count block nodes in the range, return true if more than one
307
+ var blockCount = 0;
308
+ for (var i = range.startIndex; i < range.endIndex; i++) {
309
+ if (range.parent.child(i).isBlock) {
310
+ blockCount++;
311
+ }
312
+ if (blockCount > 1) {
313
+ return true;
314
+ }
315
+ }
316
+ return false;
317
+ };
318
+ var shouldExpandSelection = function shouldExpandSelection(range, startPos) {
319
+ return !!range && isRangeSpanningMultipleNodes(range) && range.start <= startPos && range.end >= startPos + 1;
320
+ };
321
+ var calculateBlockRange = function calculateBlockRange(_ref) {
322
+ var selection = _ref.selection,
323
+ doc = _ref.doc,
324
+ resolvedStartPos = _ref.resolvedStartPos,
325
+ isShiftPressed = _ref.isShiftPressed;
326
+ if (!isShiftPressed) {
327
+ // When not pressing shift, create range including all block nodes within the selection
328
+ return selection.$from.blockRange(selection.$to);
329
+ }
330
+ if (resolvedStartPos.pos < selection.from) {
331
+ // If shift+click selecting upwards, get range from start of node to end of selection
332
+ return resolvedStartPos.blockRange(selection.$to);
333
+ }
334
+
335
+ // Shift+click selecting downwards, get range from start of selection to pos within or after node
336
+ var resolvedPosWithinOrAfterNode = doc.resolve(resolvedStartPos.pos + 1);
337
+ return selection.$from.blockRange(resolvedPosWithinOrAfterNode);
338
+ };
339
+ var createExpandedSelection = function createExpandedSelection(doc, selection, range) {
340
+ return _state.TextSelection.create(doc, Math.min(selection.from, range.start), Math.max(selection.to, range.end));
341
+ };
342
+ var createSelectionFromRange = function createSelectionFromRange(_ref2) {
343
+ var tr = _ref2.tr,
344
+ selection = _ref2.selection,
345
+ startPos = _ref2.startPos,
346
+ nodeType = _ref2.nodeType,
347
+ range = _ref2.range,
348
+ api = _ref2.api;
349
+ if (range && shouldExpandSelection(range, startPos)) {
350
+ var expandedSelection = createExpandedSelection(tr.doc, selection, range);
351
+ if (!expandedSelection.eq(tr.selection)) {
352
+ tr.setSelection(expandedSelection);
353
+ }
354
+ return tr;
355
+ }
356
+ var node = tr.doc.nodeAt(startPos);
357
+ var isEmptyNode = (node === null || node === void 0 ? void 0 : node.content.size) === 0;
358
+ if (isEmptyNode && node.type.name !== 'paragraph') {
359
+ tr.setSelection(new _state.NodeSelection(tr.doc.resolve(startPos)));
360
+ return tr;
361
+ }
362
+ return (0, _getSelection.selectNode)(tr, startPos, nodeType, api);
363
+ };
364
+ var DragHandle = exports.DragHandle = function DragHandle(_ref3) {
365
+ var _api$core4;
366
+ var view = _ref3.view,
367
+ api = _ref3.api,
368
+ formatMessage = _ref3.formatMessage,
369
+ getPos = _ref3.getPos,
370
+ anchorName = _ref3.anchorName,
371
+ nodeType = _ref3.nodeType,
372
+ handleOptions = _ref3.handleOptions,
373
+ _ref3$isTopLevelNode = _ref3.isTopLevelNode,
374
+ isTopLevelNode = _ref3$isTopLevelNode === void 0 ? true : _ref3$isTopLevelNode,
375
+ anchorRectCache = _ref3.anchorRectCache;
310
376
  var buttonRef = (0, _react.useRef)(null);
311
377
  var _useState = (0, _react.useState)(false),
312
378
  _useState2 = (0, _slicedToArray2.default)(_useState, 2),
@@ -367,14 +433,69 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
367
433
  }
368
434
  }
369
435
  }, [anchorName, nodeType, view.dom]);
370
- var handleOnClick = (0, _react.useCallback)(function (e) {
436
+ var handleOnClickNew = (0, _react.useCallback)(function (e) {
371
437
  var _api$core;
438
+ api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref4) {
439
+ var _api$analytics, _resolvedStartPos$nod, _selectionPreservatio, _api$blockControls, _api$blockControls2;
440
+ var tr = _ref4.tr;
441
+ var startPos = getPos();
442
+ if (startPos === undefined) {
443
+ return tr;
444
+ }
445
+ var resolvedStartPos = tr.doc.resolve(startPos);
446
+ api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.attachAnalyticsEvent({
447
+ eventType: _analytics.EVENT_TYPE.UI,
448
+ action: _analytics.ACTION.CLICKED,
449
+ actionSubject: _analytics.ACTION_SUBJECT.BUTTON,
450
+ actionSubjectId: _analytics.ACTION_SUBJECT_ID.ELEMENT_DRAG_HANDLE,
451
+ attributes: {
452
+ nodeDepth: resolvedStartPos.depth,
453
+ nodeType: ((_resolvedStartPos$nod = resolvedStartPos.nodeAfter) === null || _resolvedStartPos$nod === void 0 ? void 0 : _resolvedStartPos$nod.type.name) || ''
454
+ }
455
+ })(tr);
456
+ var preservedSelection = (_selectionPreservatio = _pluginKey.selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio === void 0 ? void 0 : _selectionPreservatio.preservedSelection;
457
+ var selection = preservedSelection || tr.selection;
458
+ var range = calculateBlockRange({
459
+ doc: tr.doc,
460
+ selection: selection,
461
+ resolvedStartPos: resolvedStartPos,
462
+ isShiftPressed: e.shiftKey
463
+ });
464
+ tr = createSelectionFromRange({
465
+ tr: tr,
466
+ selection: selection,
467
+ startPos: startPos,
468
+ nodeType: nodeType,
469
+ range: range,
470
+ api: api
471
+ });
472
+ api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || _api$blockControls.commands.startPreservingSelection()({
473
+ tr: tr
474
+ });
475
+ api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || _api$blockControls2.commands.toggleBlockMenu({
476
+ anchorName: anchorName,
477
+ openedViaKeyboard: false,
478
+ triggerByNode: (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_synced_block', 'isEnabled', true) ? {
479
+ nodeType: nodeType,
480
+ pos: startPos,
481
+ rootPos: tr.doc.resolve(startPos).before(1)
482
+ } : undefined
483
+ })({
484
+ tr: tr
485
+ });
486
+ tr.setMeta('scrollIntoView', false);
487
+ return tr;
488
+ });
489
+ view.focus();
490
+ }, [api, view, getPos, nodeType, anchorName]);
491
+ var handleOnClick = (0, _react.useCallback)(function (e) {
492
+ var _api$core2;
372
493
  if (!isMultiSelect) {
373
494
  setDragHandleSelected(!dragHandleSelected);
374
495
  }
375
- api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref2) {
376
- var _api$blockControls$sh, _api$analytics;
377
- var tr = _ref2.tr;
496
+ api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.execute(function (_ref5) {
497
+ var _api$blockControls$sh, _api$analytics2;
498
+ var tr = _ref5.tr;
378
499
  var startPos = getPos();
379
500
  if (startPos === undefined) {
380
501
  return tr;
@@ -403,8 +524,8 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
403
524
  rootPos: rootPos
404
525
  } : undefined;
405
526
  if (_consts2.BLOCK_MENU_ENABLED && (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1')) {
406
- var _api$blockControls;
407
- api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || _api$blockControls.commands.toggleBlockMenu({
527
+ var _api$blockControls3;
528
+ api === null || api === void 0 || (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 || _api$blockControls3.commands.toggleBlockMenu({
408
529
  anchorName: anchorName,
409
530
  triggerByNode: triggerByNode,
410
531
  openedViaKeyboard: (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true) ? false : undefined
@@ -413,8 +534,8 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
413
534
  });
414
535
  e.stopPropagation();
415
536
  } else if ((0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true)) {
416
- var _api$blockControls2;
417
- api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || _api$blockControls2.commands.toggleBlockMenu({
537
+ var _api$blockControls4;
538
+ api === null || api === void 0 || (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 || _api$blockControls4.commands.toggleBlockMenu({
418
539
  anchorName: anchorName,
419
540
  triggerByNode: triggerByNode,
420
541
  openedViaKeyboard: (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true) ? false : undefined
@@ -424,18 +545,18 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
424
545
  e.stopPropagation();
425
546
  }
426
547
  } else if (isTopLevelNode && $anchor.depth <= _consts2.DRAG_HANDLE_MAX_SHIFT_CLICK_DEPTH && e.shiftKey && (0, _platformFeatureFlags.fg)('platform_editor_elements_dnd_shift_click_select')) {
427
- var _api$blockControls3;
428
- var alignAnchorHeadToSel = (0, _selection2.alignAnchorHeadInDirectionOfPos)(tr.selection, startPos);
429
- var selectionWithExpandedHead = (0, _selection2.expandSelectionHeadToNodeAtPos)(alignAnchorHeadToSel, startPos);
548
+ var _api$blockControls5;
549
+ var alignAnchorHeadToSel = (0, _selection3.alignAnchorHeadInDirectionOfPos)(tr.selection, startPos);
550
+ var selectionWithExpandedHead = (0, _selection3.expandSelectionHeadToNodeAtPos)(alignAnchorHeadToSel, startPos);
430
551
  tr.setSelection(selectionWithExpandedHead);
431
- api === null || api === void 0 || (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 || _api$blockControls3.commands.setMultiSelectPositions()({
552
+ api === null || api === void 0 || (_api$blockControls5 = api.blockControls) === null || _api$blockControls5 === void 0 || _api$blockControls5.commands.setMultiSelectPositions()({
432
553
  tr: tr
433
554
  });
434
555
  }
435
556
  var resolvedMovingNode = tr.doc.resolve(startPos);
436
557
  var maybeNode = resolvedMovingNode.nodeAfter;
437
558
  tr.setMeta('scrollIntoView', false);
438
- api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.attachAnalyticsEvent({
559
+ api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || _api$analytics2.actions.attachAnalyticsEvent({
439
560
  eventType: _analytics.EVENT_TYPE.UI,
440
561
  action: _analytics.ACTION.CLICKED,
441
562
  actionSubject: _analytics.ACTION_SUBJECT.BUTTON,
@@ -452,10 +573,10 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
452
573
  var handleKeyDown = (0, _react.useCallback)(function (e) {
453
574
  // allow user to use spacebar to select the node
454
575
  if (!e.repeat && e.key === ' ') {
455
- var _api$core2;
576
+ var _api$core3;
456
577
  var startPos = getPos();
457
- api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.execute(function (_ref3) {
458
- var tr = _ref3.tr;
578
+ api === null || api === void 0 || (_api$core3 = api.core) === null || _api$core3 === void 0 || _api$core3.actions.execute(function (_ref6) {
579
+ var tr = _ref6.tr;
459
580
  if (startPos === undefined) {
460
581
  return tr;
461
582
  }
@@ -478,20 +599,20 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
478
599
  // return focus to editor to resume editing from caret position
479
600
  view.focus();
480
601
  }
481
- }, [getPos, api === null || api === void 0 || (_api$core3 = api.core) === null || _api$core3 === void 0 ? void 0 : _api$core3.actions, isMultiSelect, view]);
602
+ }, [getPos, api === null || api === void 0 || (_api$core4 = api.core) === null || _api$core4 === void 0 ? void 0 : _api$core4.actions, isMultiSelect, view]);
482
603
  var handleKeyDownNew = (0, _react.useCallback)(function (e) {
483
604
  // allow user to use spacebar to select the node
484
605
  if (e.key === 'Enter' || !e.repeat && e.key === ' ') {
485
- var _api$core4;
486
- if (document.activeElement !== buttonRef.current) {
606
+ var _getDocument, _api$core5;
607
+ if (((_getDocument = (0, _browserApis.getDocument)()) === null || _getDocument === void 0 ? void 0 : _getDocument.activeElement) !== buttonRef.current) {
487
608
  return;
488
609
  }
489
610
  e.preventDefault();
490
611
  e.stopPropagation();
491
612
  var startPos = getPos();
492
- api === null || api === void 0 || (_api$core4 = api.core) === null || _api$core4 === void 0 || _api$core4.actions.execute(function (_ref4) {
493
- var _api$blockControls4, _api$userIntent;
494
- var tr = _ref4.tr;
613
+ api === null || api === void 0 || (_api$core5 = api.core) === null || _api$core5 === void 0 || _api$core5.actions.execute(function (_ref7) {
614
+ var _api$blockControls6, _api$userIntent;
615
+ var tr = _ref7.tr;
495
616
  if (startPos === undefined) {
496
617
  return tr;
497
618
  }
@@ -505,7 +626,7 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
505
626
  pos: startPos,
506
627
  rootPos: rootPos
507
628
  } : undefined;
508
- api === null || api === void 0 || (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 || _api$blockControls4.commands.toggleBlockMenu({
629
+ api === null || api === void 0 || (_api$blockControls6 = api.blockControls) === null || _api$blockControls6 === void 0 || _api$blockControls6.commands.toggleBlockMenu({
509
630
  anchorName: anchorName,
510
631
  triggerByNode: triggerByNode,
511
632
  openedViaKeyboard: true
@@ -517,6 +638,20 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
517
638
  });
518
639
  return tr;
519
640
  });
641
+ } else if (e.key === 'Backspace' || e.key === 'Delete') {
642
+ e.preventDefault();
643
+ e.stopPropagation();
644
+ api === null || api === void 0 || api.core.actions.execute(function (_ref8) {
645
+ var _api$blockControls7;
646
+ var tr = _ref8.tr;
647
+ (0, _selection2.deleteSelectedRange)(tr);
648
+ api === null || api === void 0 || (_api$blockControls7 = api.blockControls) === null || _api$blockControls7 === void 0 || _api$blockControls7.commands.toggleBlockMenu({
649
+ closeMenu: true
650
+ })({
651
+ tr: tr
652
+ });
653
+ return tr;
654
+ });
520
655
  } else if (![e.altKey, e.ctrlKey, e.shiftKey].some(function (pressed) {
521
656
  return pressed;
522
657
  })) {
@@ -538,21 +673,21 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
538
673
  start: start
539
674
  };
540
675
  },
541
- onGenerateDragPreview: function onGenerateDragPreview(_ref5) {
676
+ onGenerateDragPreview: function onGenerateDragPreview(_ref9) {
542
677
  var _api$blockControls$sh2;
543
- var nativeSetDragImage = _ref5.nativeSetDragImage;
678
+ var nativeSetDragImage = _ref9.nativeSetDragImage;
544
679
  if (isMultiSelect) {
545
- var _api$core5;
546
- api === null || api === void 0 || (_api$core5 = api.core) === null || _api$core5 === void 0 || _api$core5.actions.execute(function (_ref6) {
547
- var tr = _ref6.tr;
680
+ var _api$core6;
681
+ api === null || api === void 0 || (_api$core6 = api.core) === null || _api$core6 === void 0 || _api$core6.actions.execute(function (_ref0) {
682
+ var tr = _ref0.tr;
548
683
  var handlePos = getPos();
549
684
  if (typeof handlePos !== 'number') {
550
685
  return tr;
551
686
  }
552
687
  var newHandlePosCheck = (0, _getSelection.isHandleCorrelatedToSelection)(view.state, tr.selection, handlePos);
553
688
  if (!tr.selection.empty && newHandlePosCheck) {
554
- var _api$blockControls5;
555
- api === null || api === void 0 || (_api$blockControls5 = api.blockControls) === null || _api$blockControls5 === void 0 || _api$blockControls5.commands.setMultiSelectPositions()({
689
+ var _api$blockControls8;
690
+ api === null || api === void 0 || (_api$blockControls8 = api.blockControls) === null || _api$blockControls8 === void 0 || _api$blockControls8.commands.setMultiSelectPositions()({
556
691
  tr: tr
557
692
  });
558
693
  } else if ((0, _platformFeatureFlags.fg)('platform_editor_elements_dnd_select_node_on_drag')) {
@@ -622,8 +757,8 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
622
757
  };
623
758
  }
624
759
  },
625
- render: function render(_ref7) {
626
- var container = _ref7.container;
760
+ render: function render(_ref1) {
761
+ var container = _ref1.container;
627
762
  var dom = view.dom.querySelector("[".concat((0, _domAttrName.getAnchorAttrName)(), "=\"").concat(anchorName, "\"]"));
628
763
  if (!dom) {
629
764
  return;
@@ -655,13 +790,13 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
655
790
  });
656
791
  },
657
792
  onDragStart: function onDragStart() {
658
- var _api$core6;
793
+ var _api$core7;
659
794
  if (start === undefined) {
660
795
  return;
661
796
  }
662
- api === null || api === void 0 || (_api$core6 = api.core) === null || _api$core6 === void 0 || _api$core6.actions.execute(function (_ref8) {
663
- var _api$blockControls$sh3, _api$blockControls6, _api$analytics2;
664
- var tr = _ref8.tr;
797
+ api === null || api === void 0 || (_api$core7 = api.core) === null || _api$core7 === void 0 || _api$core7.actions.execute(function (_ref10) {
798
+ var _api$blockControls$sh3, _api$blockControls9, _api$analytics3;
799
+ var tr = _ref10.tr;
665
800
  var nodeTypes, hasSelectedMultipleNodes;
666
801
  var resolvedMovingNode = tr.doc.resolve(start);
667
802
  var maybeNode = resolvedMovingNode.nodeAfter;
@@ -674,11 +809,11 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
674
809
  nodeTypes = maybeNode === null || maybeNode === void 0 ? void 0 : maybeNode.type.name;
675
810
  hasSelectedMultipleNodes = false;
676
811
  }
677
- api === null || api === void 0 || (_api$blockControls6 = api.blockControls) === null || _api$blockControls6 === void 0 || _api$blockControls6.commands.setNodeDragged(getPos, anchorName, nodeType)({
812
+ api === null || api === void 0 || (_api$blockControls9 = api.blockControls) === null || _api$blockControls9 === void 0 || _api$blockControls9.commands.setNodeDragged(getPos, anchorName, nodeType)({
678
813
  tr: tr
679
814
  });
680
815
  tr.setMeta('scrollIntoView', false);
681
- api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || _api$analytics2.actions.attachAnalyticsEvent({
816
+ api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 || _api$analytics3.actions.attachAnalyticsEvent({
682
817
  eventType: _analytics.EVENT_TYPE.UI,
683
818
  action: _analytics.ACTION.DRAGGED,
684
819
  actionSubject: _analytics.ACTION_SUBJECT.ELEMENT,
@@ -972,7 +1107,7 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
972
1107
  // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
973
1108
  ,
974
1109
  style: !(0, _experiments.editorExperiment)('platform_editor_controls', 'variant1') ? (0, _experiments.editorExperiment)('platform_editor_block_control_optimise_render', true) ? positionStyles : positionStylesOld : {},
975
- onClick: handleOnClick,
1110
+ onClick: (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true) ? handleOnClickNew : handleOnClick,
976
1111
  onKeyDown: (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true) && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true) ? handleKeyDownNew : handleKeyDown
977
1112
  // eslint-disable-next-line @atlaskit/design-system/no-direct-use-of-web-platform-drag-and-drop
978
1113
  ,
@@ -1057,15 +1192,15 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
1057
1192
  var render = isTooltip ? buttonWithTooltip() : renderButton();
1058
1193
  return (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1') ? stickyRender : render;
1059
1194
  };
1060
- var DragHandleWithVisibility = exports.DragHandleWithVisibility = function DragHandleWithVisibility(_ref9) {
1061
- var view = _ref9.view,
1062
- api = _ref9.api,
1063
- formatMessage = _ref9.formatMessage,
1064
- getPos = _ref9.getPos,
1065
- anchorName = _ref9.anchorName,
1066
- nodeType = _ref9.nodeType,
1067
- handleOptions = _ref9.handleOptions,
1068
- anchorRectCache = _ref9.anchorRectCache;
1195
+ var DragHandleWithVisibility = exports.DragHandleWithVisibility = function DragHandleWithVisibility(_ref11) {
1196
+ var view = _ref11.view,
1197
+ api = _ref11.api,
1198
+ formatMessage = _ref11.formatMessage,
1199
+ getPos = _ref11.getPos,
1200
+ anchorName = _ref11.anchorName,
1201
+ nodeType = _ref11.nodeType,
1202
+ handleOptions = _ref11.handleOptions,
1203
+ anchorRectCache = _ref11.anchorRectCache;
1069
1204
  return (0, _react2.jsx)(_visibilityContainer.VisibilityContainer, {
1070
1205
  api: api
1071
1206
  }, (0, _react2.jsx)(DragHandle, {
@@ -12,6 +12,8 @@ import { canMoveNodeUpOrDown } from './editor-commands/utils/move-node-utils';
12
12
  import { firstNodeDecPlugin } from './pm-plugins/first-node-dec-plugin';
13
13
  import { createInteractionTrackingPlugin, interactionTrackingPluginKey } from './pm-plugins/interaction-tracking/pm-plugin';
14
14
  import { createPlugin, key } from './pm-plugins/main';
15
+ import { startPreservingSelection, stopPreservingSelection } from './pm-plugins/selection-preservation/editor-commands';
16
+ import { createSelectionPreservationPlugin } from './pm-plugins/selection-preservation/pm-plugin';
15
17
  import { selectNode } from './pm-plugins/utils/getSelection';
16
18
  import BlockMenu from './ui/block-menu';
17
19
  import { DragHandleMenu } from './ui/drag-handle-menu';
@@ -34,6 +36,12 @@ export const blockControlsPlugin = ({
34
36
  plugin: createInteractionTrackingPlugin
35
37
  });
36
38
  }
39
+ if (expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
40
+ pmPlugins.push({
41
+ name: 'blockControlsSelectionPreservationPlugin',
42
+ plugin: createSelectionPreservationPlugin
43
+ });
44
+ }
37
45
 
38
46
  // platform_editor_controls note: quick insert rendering fixes
39
47
  if (areToolbarFlagsEnabled(Boolean(api === null || api === void 0 ? void 0 : api.toolbar))) {
@@ -233,7 +241,9 @@ export const blockControlsPlugin = ({
233
241
  },
234
242
  moveNodeWithBlockMenu: direction => {
235
243
  return moveNodeWithBlockMenu(api, direction);
236
- }
244
+ },
245
+ startPreservingSelection: () => startPreservingSelection,
246
+ stopPreservingSelection: () => stopPreservingSelection
237
247
  },
238
248
  getSharedState(editorState) {
239
249
  var _key$getState$isMenuO, _key$getState, _key$getState$menuTri, _key$getState2, _key$getState$menuTri2, _key$getState3, _key$getState$blockMe, _key$getState4, _key$getState$activeN, _key$getState5, _key$getState$activeD, _key$getState6, _key$getState$isDragg, _key$getState7, _key$getState$isPMDra, _key$getState8, _key$getState$multiSe, _key$getState9, _key$getState$isShift, _key$getState0, _key$getState$lastDra, _key$getState1, _interactionTrackingP, _key$getState$isSelec, _key$getState10;
@@ -775,8 +775,11 @@ export const createPlugin = (api, getIntl, nodeViewPortalProviderAPI) => {
775
775
  api === null || api === void 0 ? void 0 : api.core.actions.execute(api === null || api === void 0 ? void 0 : api.blockControls.commands.setSelectedViaDragHandle(isDragHandle));
776
776
  }
777
777
  if ((event.key === 'ArrowLeft' || event.key === 'ArrowRight' || event.key === 'ArrowDown' || event.key === 'ArrowUp') && editorExperiment('platform_editor_controls', 'variant1')) {
778
- var _api$blockControls$sh2;
779
- if (api !== null && api !== void 0 && (_api$blockControls$sh2 = api.blockControls.sharedState.currentState()) !== null && _api$blockControls$sh2 !== void 0 && _api$blockControls$sh2.isSelectedViaDragHandle) {
778
+ var _api$blockControls$sh2, _api$blockControls$sh3;
779
+ const isBlockMenuOpen = (api === null || api === void 0 ? void 0 : (_api$blockControls$sh2 = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh2 === void 0 ? void 0 : _api$blockControls$sh2.isMenuOpen) && expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true) && expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true);
780
+ // when block menu is just open, and we press arrow keys, we want to use the arrow keys to navigate the block menu
781
+ // in this scenario, isSelectedViaDragHandle should not be set to false
782
+ if (api !== null && api !== void 0 && (_api$blockControls$sh3 = api.blockControls.sharedState.currentState()) !== null && _api$blockControls$sh3 !== void 0 && _api$blockControls$sh3.isSelectedViaDragHandle && !isBlockMenuOpen) {
780
783
  api === null || api === void 0 ? void 0 : api.core.actions.execute(api === null || api === void 0 ? void 0 : api.blockControls.commands.setSelectedViaDragHandle(false));
781
784
  }
782
785
  }
@@ -800,8 +803,11 @@ export const createPlugin = (api, getIntl, nodeViewPortalProviderAPI) => {
800
803
  api === null || api === void 0 ? void 0 : api.core.actions.execute(api === null || api === void 0 ? void 0 : api.blockControls.commands.setSelectedViaDragHandle(isDragHandle));
801
804
  }
802
805
  if ((event.key === 'ArrowLeft' || event.key === 'ArrowRight' || event.key === 'ArrowDown' || event.key === 'ArrowUp') && editorExperiment('platform_editor_controls', 'variant1')) {
803
- var _api$blockControls$sh3;
804
- if (api !== null && api !== void 0 && (_api$blockControls$sh3 = api.blockControls.sharedState.currentState()) !== null && _api$blockControls$sh3 !== void 0 && _api$blockControls$sh3.isSelectedViaDragHandle) {
806
+ var _api$blockControls$sh4, _api$blockControls$sh5;
807
+ const isBlockMenuOpen = (api === null || api === void 0 ? void 0 : (_api$blockControls$sh4 = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh4 === void 0 ? void 0 : _api$blockControls$sh4.isMenuOpen) && expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true) && expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true);
808
+ // when block menu is just open, and we press arrow keys, we want to use the arrow keys to navigate the block menu
809
+ // in this scenario, isSelectedViaDragHandle should not be set to false
810
+ if (api !== null && api !== void 0 && (_api$blockControls$sh5 = api.blockControls.sharedState.currentState()) !== null && _api$blockControls$sh5 !== void 0 && _api$blockControls$sh5.isSelectedViaDragHandle && !isBlockMenuOpen) {
805
811
  api === null || api === void 0 ? void 0 : api.core.actions.execute(api === null || api === void 0 ? void 0 : api.blockControls.commands.setSelectedViaDragHandle(false));
806
812
  }
807
813
  }
@@ -0,0 +1,28 @@
1
+ import { selectionPreservationPluginKey } from './plugin-key';
2
+ /**
3
+ * Start preserving the selection when a UI interaction requires it
4
+ *
5
+ * e.g., block menu open, drag-and-drop in progress
6
+ */
7
+ export const startPreservingSelection = ({
8
+ tr
9
+ }) => {
10
+ const meta = {
11
+ type: 'startPreserving'
12
+ };
13
+ return tr.setMeta(selectionPreservationPluginKey, meta);
14
+ };
15
+
16
+ /**
17
+ * Stop preserving the selection when a UI interaction completes
18
+ *
19
+ * e.g., block menu closed, drag-and-drop ended
20
+ */
21
+ export const stopPreservingSelection = ({
22
+ tr
23
+ }) => {
24
+ const meta = {
25
+ type: 'stopPreserving'
26
+ };
27
+ return tr.setMeta(selectionPreservationPluginKey, meta);
28
+ };
@@ -0,0 +1,2 @@
1
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
2
+ export const selectionPreservationPluginKey = new PluginKey('selectionPreservationPlugin');
@@ -0,0 +1,92 @@
1
+ import { logException } from '@atlaskit/editor-common/monitoring';
2
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
4
+ import { stopPreservingSelection } from './editor-commands';
5
+ import { selectionPreservationPluginKey } from './plugin-key';
6
+ import { getSelectionPreservationMeta, hasUserSelectionChange } from './utils';
7
+
8
+ /**
9
+ * Selection Preservation Plugin for ProseMirror Editor
10
+ *
11
+ * Solves a ProseMirror limitation where TextSelection cannot include positions at node boundaries
12
+ * (like media/images). When a selection spans text + media nodes, subsequent transactions cause
13
+ * ProseMirror to collapse the selection to the nearest inline position, excluding the media node.
14
+ * This is problematic for features like block menus and drag-and-drop that need stable multi-node
15
+ * selections while performing operations.
16
+ *
17
+ * The plugin works in three phases:
18
+ * (1) Explicitly save a selection via startPreservingSelection() when opening block menus or starting drag operations.
19
+ * (2) Map the saved selection through document changes to keep positions valid.
20
+ * (3) Detect when transactions collapse the selection and restore it via appendTransaction().
21
+ *
22
+ * Stops preserving via stopPreservingSelection() when the menu closes or operation completes.
23
+ *
24
+ * Commands: startPreservingSelection() to begin preservation, stopPreservingSelection() to end it.
25
+ *
26
+ * NOTE: Only use when the UI blocks user selection changes. For example: when a block menu overlay
27
+ * is open (editor becomes non-interactive), during drag-and-drop operations (user is mid-drag), or
28
+ * when modal dialogs are active. In these states, any selection changes are from ProseMirror's
29
+ * internal behavior (not user input) and should be prevented. Do not use during normal editing.
30
+ */
31
+ export const createSelectionPreservationPlugin = () => {
32
+ return new SafePlugin({
33
+ key: selectionPreservationPluginKey,
34
+ state: {
35
+ init() {
36
+ return {
37
+ preservedSelection: undefined
38
+ };
39
+ },
40
+ apply(tr, pluginState) {
41
+ const meta = getSelectionPreservationMeta(tr);
42
+ const newState = {
43
+ ...pluginState
44
+ };
45
+ if ((meta === null || meta === void 0 ? void 0 : meta.type) === 'startPreserving') {
46
+ newState.preservedSelection = new TextSelection(tr.selection.$from, tr.selection.$to);
47
+ } else if ((meta === null || meta === void 0 ? void 0 : meta.type) === 'stopPreserving') {
48
+ newState.preservedSelection = undefined;
49
+ }
50
+ if (newState.preservedSelection && tr.docChanged) {
51
+ const mapped = new TextSelection(newState.preservedSelection.$from, newState.preservedSelection.$to);
52
+ mapped.map(tr.doc, tr.mapping);
53
+ if (mapped.from >= 0 && mapped.to <= tr.doc.content.size && mapped.from !== mapped.to) {
54
+ newState.preservedSelection = mapped;
55
+ } else if (mapped.from === mapped.to) {
56
+ // If selection has collapsed to a cursor, e.g. after deleting the selection, stop preserving
57
+ newState.preservedSelection = undefined;
58
+ }
59
+ }
60
+ return newState;
61
+ }
62
+ },
63
+ appendTransaction(transactions, _oldState, newState) {
64
+ const pluginState = selectionPreservationPluginKey.getState(newState);
65
+ const savedSel = pluginState === null || pluginState === void 0 ? void 0 : pluginState.preservedSelection;
66
+ if (!savedSel) {
67
+ return null;
68
+ }
69
+ if (hasUserSelectionChange(transactions)) {
70
+ // Auto-stop if user explicitly changes selection
71
+ return stopPreservingSelection({
72
+ tr: newState.tr
73
+ });
74
+ }
75
+ const currSel = newState.selection;
76
+ const wasEmptySelection = savedSel.from === savedSel.to;
77
+ const selectionUnchanged = currSel.from === savedSel.from && currSel.to === savedSel.to;
78
+ const selectionInvalid = savedSel.from < 0 || savedSel.to > newState.doc.content.size;
79
+ if (wasEmptySelection || selectionUnchanged || selectionInvalid) {
80
+ return null;
81
+ }
82
+ try {
83
+ return newState.tr.setSelection(TextSelection.create(newState.doc, savedSel.from, savedSel.to));
84
+ } catch (error) {
85
+ logException(error, {
86
+ location: 'editor-plugin-block-controls/SelectionPreservationPlugin'
87
+ });
88
+ }
89
+ return null;
90
+ }
91
+ });
92
+ };
@@ -0,0 +1,16 @@
1
+ import { selectionPreservationPluginKey } from './plugin-key';
2
+ /**
3
+ * Detects if any of the transactions include user-driven selection changes.
4
+ *
5
+ * @param transactions The list of transactions to check.
6
+ * @returns True if any transaction includes a user-driven selection change, otherwise false.
7
+ */
8
+ export const hasUserSelectionChange = transactions => {
9
+ return transactions.some(tr => tr.getMeta('pointer') || tr.getMeta('uiEvent') || tr.getMeta('paste') || tr.getMeta('cut') || tr.getMeta('composition') ||
10
+ // IME input
11
+ // Keyboard events that change selection
12
+ tr.getMeta('addToHistory') && tr.selectionSet);
13
+ };
14
+ export const getSelectionPreservationMeta = tr => {
15
+ return tr.getMeta(selectionPreservationPluginKey);
16
+ };