@atlaskit/editor-plugin-block-controls 13.1.12 → 13.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @atlaskit/editor-plugin-block-controls
2
2
 
3
+ ## 13.1.14
4
+
5
+ ### Patch Changes
6
+
7
+ - [`79402109ef24e`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/79402109ef24e) -
8
+ fix block controls reliable anchor glitch
9
+ - Updated dependencies
10
+
11
+ ## 13.1.13
12
+
13
+ ### Patch Changes
14
+
15
+ - [`0b938a9924936`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/0b938a9924936) -
16
+ Clean up feature gate `platform_editor_block_menu_jira_patch_2`.
17
+ - Updated dependencies
18
+
3
19
  ## 13.1.12
4
20
 
5
21
  ### Patch Changes
@@ -442,32 +442,34 @@ var _apply = exports.apply = function apply(api, formatMessage, tr, currentState
442
442
  }
443
443
  }
444
444
  if (shouldRemoveHandle) {
445
- var _activeNode5, _activeNode6, _activeNode7;
445
+ var _activeNode5, _activeNode6;
446
446
  var oldHandle = (0, _decorationsDragHandle.findHandleDec)(decorations, (_activeNode5 = activeNode) === null || _activeNode5 === void 0 ? void 0 : _activeNode5.pos, (_activeNode6 = activeNode) === null || _activeNode6 === void 0 ? void 0 : _activeNode6.pos);
447
447
  decorations = decorations.remove(oldHandle);
448
448
  // When removing the handle, also remove the anchor-marker node decorations
449
449
  // (data-active-drag-handle / data-active-quick-insert) so the DOM attributes
450
450
  // don't linger on nodes that are no longer active.
451
- if ((0, _expValEquals.expValEquals)('platform_editor_controls_reliable_anchor', 'isEnabled', true) && ((_activeNode7 = activeNode) === null || _activeNode7 === void 0 ? void 0 : _activeNode7.pos) !== undefined) {
452
- var oldActiveNodeDec = (0, _decorationsDragHandle.findActiveDragHandleNodeDec)(decorations, activeNode.pos, activeNode.pos);
451
+ // Use full-range search (0..contentSize) rather than a point-range at activeNode.pos/rootPos:
452
+ // after a doc change (e.g. pressing Enter splits a node), DecorationSet.map() can shift the
453
+ // decoration to a different position, so a point-range search would miss it and leave a stale
454
+ // attribute on the wrong DOM node.
455
+ if ((0, _expValEquals.expValEquals)('platform_editor_controls_reliable_anchor', 'isEnabled', true)) {
456
+ var oldActiveNodeDec = (0, _decorationsDragHandle.findActiveDragHandleNodeDec)(decorations, 0, newState.doc.content.size);
453
457
  decorations = decorations.remove(oldActiveNodeDec);
454
- if (activeNode.rootPos !== undefined) {
455
- var oldActiveQuickInsertDec = (0, _decorationsQuickInsertButton.findActiveQuickInsertNodeDec)(decorations, activeNode.rootPos, activeNode.rootPos);
456
- decorations = decorations.remove(oldActiveQuickInsertDec);
457
- }
458
+ var oldActiveQuickInsertDec = (0, _decorationsQuickInsertButton.findActiveQuickInsertNodeDec)(decorations, 0, newState.doc.content.size);
459
+ decorations = decorations.remove(oldActiveQuickInsertDec);
458
460
  }
459
461
  // platform_editor_controls note: enables quick insert
460
462
  if (flags.toolbarFlagsEnabled && quickInsertButtonEnabled) {
461
- var _activeNode8, _activeNode9;
462
- var oldQuickInsertButton = (0, _decorationsQuickInsertButton.findQuickInsertInsertButtonDecoration)(decorations, (_activeNode8 = activeNode) === null || _activeNode8 === void 0 ? void 0 : _activeNode8.rootPos, (_activeNode9 = activeNode) === null || _activeNode9 === void 0 ? void 0 : _activeNode9.rootPos);
463
+ var _activeNode7, _activeNode8;
464
+ var oldQuickInsertButton = (0, _decorationsQuickInsertButton.findQuickInsertInsertButtonDecoration)(decorations, (_activeNode7 = activeNode) === null || _activeNode7 === void 0 ? void 0 : _activeNode7.rootPos, (_activeNode8 = activeNode) === null || _activeNode8 === void 0 ? void 0 : _activeNode8.rootPos);
463
465
  decorations = decorations.remove(oldQuickInsertButton);
464
466
  var _iterator = _createForOfIteratorHelper(nodeDecorationRegistry),
465
467
  _step;
466
468
  try {
467
469
  var _loop2 = function _loop2() {
468
- var _activeNode10, _activeNode11;
470
+ var _activeNode1, _activeNode10;
469
471
  var factory = _step.value;
470
- var old = decorations.find((_activeNode10 = activeNode) === null || _activeNode10 === void 0 ? void 0 : _activeNode10.rootPos, (_activeNode11 = activeNode) === null || _activeNode11 === void 0 ? void 0 : _activeNode11.rootPos, function (spec) {
472
+ var old = decorations.find((_activeNode1 = activeNode) === null || _activeNode1 === void 0 ? void 0 : _activeNode1.rootPos, (_activeNode10 = activeNode) === null || _activeNode10 === void 0 ? void 0 : _activeNode10.rootPos, function (spec) {
471
473
  return spec.type === factory.type;
472
474
  });
473
475
  decorations = decorations.remove(old);
@@ -487,8 +489,8 @@ var _apply = exports.apply = function apply(api, formatMessage, tr, currentState
487
489
  var _loop = function _loop() {
488
490
  var factory = _step2.value;
489
491
  if (factory.showInViewMode) {
490
- var _activeNode0, _activeNode1;
491
- var old = decorations.find((_activeNode0 = activeNode) === null || _activeNode0 === void 0 ? void 0 : _activeNode0.rootPos, (_activeNode1 = activeNode) === null || _activeNode1 === void 0 ? void 0 : _activeNode1.rootPos, function (spec) {
492
+ var _activeNode9, _activeNode0;
493
+ var old = decorations.find((_activeNode9 = activeNode) === null || _activeNode9 === void 0 ? void 0 : _activeNode9.rootPos, (_activeNode0 = activeNode) === null || _activeNode0 === void 0 ? void 0 : _activeNode0.rootPos, function (spec) {
492
494
  return spec.type === factory.type;
493
495
  });
494
496
  decorations = decorations.remove(old);
@@ -507,8 +509,8 @@ var _apply = exports.apply = function apply(api, formatMessage, tr, currentState
507
509
  } else if (api) {
508
510
  var _latestActiveNode5, _latestActiveNode10;
509
511
  if (shouldRecreateHandle && (!rightSideControlsEnabled || !isViewMode)) {
510
- var _activeNode12, _activeNode13, _latestActiveNode, _latestActiveNode2, _latestActiveNode3, _latestActiveNode4;
511
- var _oldHandle = (0, _decorationsDragHandle.findHandleDec)(decorations, (_activeNode12 = activeNode) === null || _activeNode12 === void 0 ? void 0 : _activeNode12.pos, (_activeNode13 = activeNode) === null || _activeNode13 === void 0 ? void 0 : _activeNode13.pos);
512
+ var _activeNode11, _activeNode12, _latestActiveNode, _latestActiveNode2, _latestActiveNode3, _latestActiveNode4;
513
+ var _oldHandle = (0, _decorationsDragHandle.findHandleDec)(decorations, (_activeNode11 = activeNode) === null || _activeNode11 === void 0 ? void 0 : _activeNode11.pos, (_activeNode12 = activeNode) === null || _activeNode12 === void 0 ? void 0 : _activeNode12.pos);
512
514
  decorations = decorations.remove(_oldHandle);
513
515
  var handleDec = (0, _decorationsDragHandle.dragHandleDecoration)({
514
516
  api: api,
@@ -522,33 +524,49 @@ var _apply = exports.apply = function apply(api, formatMessage, tr, currentState
522
524
  editorState: newState
523
525
  });
524
526
  decorations = decorations.add(newState.doc, [handleDec]);
525
- if ((0, _expValEquals.expValEquals)('platform_editor_controls_reliable_anchor', 'isEnabled', true)) {
526
- // Recreate the drag handle node decoration when the edit-mode node itself changed
527
- // or its content was modified. Other triggers (editorSizeChanged, handleNeedsRedraw)
528
- // don't move the decoration DecorationSet.map() already remaps it correctly.
529
- var needsNodeDecUpdate = activeNodeChanged || isActiveNodeModified;
530
- if (needsNodeDecUpdate) {
531
- var _newState$doc$nodeAt;
532
- var nodeSize = (_newState$doc$nodeAt = newState.doc.nodeAt(latestActiveNode.pos)) === null || _newState$doc$nodeAt === void 0 ? void 0 : _newState$doc$nodeAt.nodeSize;
533
- if (nodeSize !== undefined) {
534
- var _activeNode14, _activeNode15;
535
- var _oldActiveNodeDec = (0, _decorationsDragHandle.findActiveDragHandleNodeDec)(decorations, (_activeNode14 = activeNode) === null || _activeNode14 === void 0 ? void 0 : _activeNode14.pos, (_activeNode15 = activeNode) === null || _activeNode15 === void 0 ? void 0 : _activeNode15.pos);
527
+ }
528
+ if ((0, _expValEquals.expValEquals)('platform_editor_controls_reliable_anchor', 'isEnabled', true) && latestActiveNode) {
529
+ // Recreate the drag handle node decoration when the active node changed,
530
+ // its content was modified, or the document changed (e.g. Enter splits a node).
531
+ // This runs independently of shouldRecreateHandle so that doc changes (like pressing
532
+ // Enter) correctly refresh the decoration even when the handle widget doesn't move.
533
+ // DecorationSet.map() can misplace the decoration after node splits/inserts.
534
+ var needsNodeDecUpdate = activeNodeChanged || isActiveNodeModified || tr.docChanged;
535
+ if (needsNodeDecUpdate) {
536
+ var _newState$doc$nodeAt;
537
+ var nodeSize = (_newState$doc$nodeAt = newState.doc.nodeAt(latestActiveNode.pos)) === null || _newState$doc$nodeAt === void 0 ? void 0 : _newState$doc$nodeAt.nodeSize;
538
+ if (nodeSize !== undefined) {
539
+ // Search the full doc range: after DecorationSet.map(), the decoration may have
540
+ // shifted away from activeNode?.pos (e.g. a node split moves it to the new paragraph).
541
+ var _oldActiveNodeDec = (0, _decorationsDragHandle.findActiveDragHandleNodeDec)(decorations, 0, newState.doc.content.size);
542
+ // Skip remove+add if the decoration is already at the correct position.
543
+ // On pure text keystrokes, DecorationSet.map() keeps the decoration correctly
544
+ // placed, so we avoid unnecessary churn on every character typed.
545
+ var dragHandleDecAlreadyCorrect = _oldActiveNodeDec.length === 1 && _oldActiveNodeDec[0].from === latestActiveNode.pos && _oldActiveNodeDec[0].to === latestActiveNode.pos + nodeSize;
546
+ if (!dragHandleDecAlreadyCorrect) {
536
547
  decorations = decorations.remove(_oldActiveNodeDec);
537
548
  decorations = decorations.add(newState.doc, [(0, _decorationsDragHandle.createActiveDragHandleNodeDecoration)(latestActiveNode.pos, nodeSize)]);
538
549
  }
539
550
  }
551
+ }
540
552
 
541
- // The quick-insert decoration lives on the root node (rootPos), which can change
542
- // independently of the edit-mode node — e.g. when only editorSizeChanged fires but
543
- // rootActiveNodeChanged is also true. So we use a separate, broader guard that
544
- // includes rootActiveNodeChanged to avoid leaving the attribute on a stale root node.
545
- var needsQuickInsertDecUpdate = activeNodeChanged || isActiveNodeModified || rootActiveNodeChanged;
546
- if (needsQuickInsertDecUpdate && latestActiveNode.rootPos !== undefined) {
547
- var _newState$doc$nodeAt2;
548
- var rootNodeSize = (_newState$doc$nodeAt2 = newState.doc.nodeAt(latestActiveNode.rootPos)) === null || _newState$doc$nodeAt2 === void 0 ? void 0 : _newState$doc$nodeAt2.nodeSize;
549
- if (rootNodeSize !== undefined) {
550
- var _activeNode16, _activeNode17;
551
- var _oldActiveQuickInsertDec = (0, _decorationsQuickInsertButton.findActiveQuickInsertNodeDec)(decorations, (_activeNode16 = activeNode) === null || _activeNode16 === void 0 ? void 0 : _activeNode16.rootPos, (_activeNode17 = activeNode) === null || _activeNode17 === void 0 ? void 0 : _activeNode17.rootPos);
553
+ // The quick-insert decoration lives on the root node (rootPos), which can change
554
+ // independently of the edit-mode node — e.g. when only editorSizeChanged fires but
555
+ // rootActiveNodeChanged is also true. So we use a separate, broader guard that
556
+ // includes rootActiveNodeChanged to avoid leaving the attribute on a stale root node.
557
+ // tr.docChanged is included for the same reason as needsNodeDecUpdate: DecorationSet.map()
558
+ // can misplace the decoration after a node split, so we must refresh it on every doc change.
559
+ var needsQuickInsertDecUpdate = activeNodeChanged || isActiveNodeModified || rootActiveNodeChanged || tr.docChanged;
560
+ if (needsQuickInsertDecUpdate && latestActiveNode.rootPos !== undefined) {
561
+ var _newState$doc$nodeAt2;
562
+ var rootNodeSize = (_newState$doc$nodeAt2 = newState.doc.nodeAt(latestActiveNode.rootPos)) === null || _newState$doc$nodeAt2 === void 0 ? void 0 : _newState$doc$nodeAt2.nodeSize;
563
+ if (rootNodeSize !== undefined) {
564
+ var _oldActiveQuickInsertDec = (0, _decorationsQuickInsertButton.findActiveQuickInsertNodeDec)(decorations, 0, newState.doc.content.size);
565
+ // Skip remove+add if the decoration is already at the correct position.
566
+ // On pure text keystrokes, DecorationSet.map() keeps the decoration correctly
567
+ // placed, so we avoid unnecessary churn on every character typed.
568
+ var quickInsertDecAlreadyCorrect = _oldActiveQuickInsertDec.length === 1 && _oldActiveQuickInsertDec[0].from === latestActiveNode.rootPos && _oldActiveQuickInsertDec[0].to === latestActiveNode.rootPos + rootNodeSize;
569
+ if (!quickInsertDecAlreadyCorrect) {
552
570
  decorations = decorations.remove(_oldActiveQuickInsertDec);
553
571
  decorations = decorations.add(newState.doc, [(0, _decorationsQuickInsertButton.createActiveQuickInsertNodeDecoration)(latestActiveNode.rootPos, rootNodeSize)]);
554
572
  }
@@ -558,8 +576,8 @@ var _apply = exports.apply = function apply(api, formatMessage, tr, currentState
558
576
  if (shouldRecreateQuickInsertButton && ((_latestActiveNode5 = latestActiveNode) === null || _latestActiveNode5 === void 0 ? void 0 : _latestActiveNode5.rootPos) !== undefined &&
559
577
  // platform_editor_controls note: enables quick insert
560
578
  flags.toolbarFlagsEnabled && quickInsertButtonEnabled && (!rightSideControlsEnabled || !isViewMode)) {
561
- var _activeNode18, _activeNode19, _latestActiveNode6, _latestActiveNode7, _latestActiveNode8, _latestActiveNode9, _latestActiveNode0, _latestActiveNode1;
562
- var _oldQuickInsertButton = (0, _decorationsQuickInsertButton.findQuickInsertInsertButtonDecoration)(decorations, (_activeNode18 = activeNode) === null || _activeNode18 === void 0 ? void 0 : _activeNode18.rootPos, (_activeNode19 = activeNode) === null || _activeNode19 === void 0 ? void 0 : _activeNode19.rootPos);
579
+ var _activeNode13, _activeNode14, _latestActiveNode6, _latestActiveNode7, _latestActiveNode8, _latestActiveNode9, _latestActiveNode0, _latestActiveNode1;
580
+ var _oldQuickInsertButton = (0, _decorationsQuickInsertButton.findQuickInsertInsertButtonDecoration)(decorations, (_activeNode13 = activeNode) === null || _activeNode13 === void 0 ? void 0 : _activeNode13.rootPos, (_activeNode14 = activeNode) === null || _activeNode14 === void 0 ? void 0 : _activeNode14.rootPos);
563
581
  decorations = decorations.remove(_oldQuickInsertButton);
564
582
  var quickInsertButton = (0, _decorationsQuickInsertButton.quickInsertButtonDecoration)({
565
583
  api: api,
@@ -578,12 +596,16 @@ var _apply = exports.apply = function apply(api, formatMessage, tr, currentState
578
596
  // Update quick insert node decoration when the quick insert button is recreated but
579
597
  // the drag handle was NOT recreated (shouldRecreateHandle was false). When
580
598
  // shouldRecreateHandle is true, the block above already handles this.
581
- if ((0, _expValEquals.expValEquals)('platform_editor_controls_reliable_anchor', 'isEnabled', true) && !shouldRecreateHandle && ((_latestActiveNode1 = latestActiveNode) === null || _latestActiveNode1 === void 0 ? void 0 : _latestActiveNode1.rootPos) !== undefined) {
582
- var _activeNode20, _newState$doc$nodeAt3;
583
- if (((_activeNode20 = activeNode) === null || _activeNode20 === void 0 ? void 0 : _activeNode20.rootPos) !== undefined) {
584
- var _oldActiveQuickInsertDec2 = (0, _decorationsQuickInsertButton.findActiveQuickInsertNodeDec)(decorations, activeNode.rootPos, activeNode.rootPos);
585
- decorations = decorations.remove(_oldActiveQuickInsertDec2);
586
- }
599
+ // Also skip when the needsQuickInsertDecUpdate block above already handled the update
600
+ // (i.e. activeNodeChanged || isActiveNodeModified || rootActiveNodeChanged || tr.docChanged)
601
+ // running both would remove and re-add a duplicate decoration.
602
+ if ((0, _expValEquals.expValEquals)('platform_editor_controls_reliable_anchor', 'isEnabled', true) && !shouldRecreateHandle && !(activeNodeChanged || isActiveNodeModified || rootActiveNodeChanged || tr.docChanged) && ((_latestActiveNode1 = latestActiveNode) === null || _latestActiveNode1 === void 0 ? void 0 : _latestActiveNode1.rootPos) !== undefined) {
603
+ var _newState$doc$nodeAt3;
604
+ // Use full-range search (0..contentSize) rather than a point-range at activeNode.rootPos:
605
+ // after a doc change, DecorationSet.map() can shift the decoration to a different position,
606
+ // so a point-range search would miss it and leave a stale attribute on the wrong DOM node.
607
+ var _oldActiveQuickInsertDec2 = (0, _decorationsQuickInsertButton.findActiveQuickInsertNodeDec)(decorations, 0, newState.doc.content.size);
608
+ decorations = decorations.remove(_oldActiveQuickInsertDec2);
587
609
  var _rootNodeSize = (_newState$doc$nodeAt3 = newState.doc.nodeAt(latestActiveNode.rootPos)) === null || _newState$doc$nodeAt3 === void 0 ? void 0 : _newState$doc$nodeAt3.nodeSize;
588
610
  if (_rootNodeSize !== undefined) {
589
611
  decorations = decorations.add(newState.doc, [(0, _decorationsQuickInsertButton.createActiveQuickInsertNodeDecoration)(latestActiveNode.rootPos, _rootNodeSize)]);
@@ -594,7 +616,7 @@ var _apply = exports.apply = function apply(api, formatMessage, tr, currentState
594
616
  _step3;
595
617
  try {
596
618
  var _loop3 = function _loop3() {
597
- var _activeNode21, _activeNode22;
619
+ var _activeNode15, _activeNode16;
598
620
  var factory = _step3.value;
599
621
  if (!latestActiveNode || latestActiveNode.rootPos === undefined) {
600
622
  return 0; // continue
@@ -608,7 +630,7 @@ var _apply = exports.apply = function apply(api, formatMessage, tr, currentState
608
630
  rootAnchorName: latestActiveNode.rootAnchorName,
609
631
  rootNodeType: latestActiveNode.rootNodeType
610
632
  };
611
- var old = decorations.find((_activeNode21 = activeNode) === null || _activeNode21 === void 0 ? void 0 : _activeNode21.rootPos, (_activeNode22 = activeNode) === null || _activeNode22 === void 0 ? void 0 : _activeNode22.rootPos, function (spec) {
633
+ var old = decorations.find((_activeNode15 = activeNode) === null || _activeNode15 === void 0 ? void 0 : _activeNode15.rootPos, (_activeNode16 = activeNode) === null || _activeNode16 === void 0 ? void 0 : _activeNode16.rootPos, function (spec) {
612
634
  return spec.type === factory.type;
613
635
  });
614
636
  decorations = decorations.remove(old);
@@ -8,7 +8,6 @@ var _blockType = require("@atlaskit/editor-common/block-type");
8
8
  var _selection = require("@atlaskit/editor-common/selection");
9
9
  var _state = require("@atlaskit/editor-prosemirror/state");
10
10
  var _utils = require("@atlaskit/editor-tables/utils");
11
- var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
12
11
  var _main = require("../main");
13
12
  var _getSelection = require("./getSelection");
14
13
  var getMultiSelectionIfPosInside = exports.getMultiSelectionIfPosInside = function getMultiSelectionIfPosInside(api, pos, tr) {
@@ -99,7 +98,7 @@ var mapPreservedSelection = exports.mapPreservedSelection = function mapPreserve
99
98
  // When lastNodeMustBeParagraph inserts a trailing paragraph exactly at the end boundary
100
99
  // of a preserved block selection, keep the mapped end position on the left side of the
101
100
  // inserted paragraph so it is not included in the preserved selection.
102
- var shouldTrimTrailingParagraph = (0, _platformFeatureFlags.fg)('platform_editor_block_menu_jira_patch_2') && tr.getMeta(_blockType.INSERTED_TRAILING_PARAGRAPH_TO_LAST_NODE_META) === true;
101
+ var shouldTrimTrailingParagraph = tr.getMeta(_blockType.INSERTED_TRAILING_PARAGRAPH_TO_LAST_NODE_META) === true;
103
102
  var to = shouldTrimTrailingParagraph ? mapping.map(selection.to, -1) : mapping.map(selection.to);
104
103
  var isSelectionEmpty = from === to;
105
104
  var wasSelectionEmpty = selection.from === selection.to;
@@ -415,6 +415,10 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
415
415
  _useState8 = (0, _slicedToArray2.default)(_useState7, 2),
416
416
  positionStylesOld = _useState8[0],
417
417
  setPositionStylesOld = _useState8[1];
418
+ // Tracks whether the initial position calculation has been performed at least once.
419
+ // The reliable-anchor early-return optimisation must not fire before the first calculation,
420
+ // otherwise positionStylesOld stays as { display: 'none' } and the handle is never shown.
421
+ var hasCalculatedInitialPosition = (0, _react.useRef)(false);
418
422
  var _useState9 = (0, _react.useState)(Boolean(handleOptions === null || handleOptions === void 0 ? void 0 : handleOptions.isFocused)),
419
423
  _useState0 = (0, _slicedToArray2.default)(_useState9, 2),
420
424
  isFocused = _useState0[0],
@@ -901,7 +905,17 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
901
905
  top: (0, _dragHandlePositions.getTopPosition)(dom, nodeType)
902
906
  }, height);
903
907
  }, [anchorName, getPos, view, nodeType, blockCardWidth, macroInteractionUpdates, anchorRectCache, isTopLevelNodeValue, isLayoutColumn]);
908
+ var isReliableAnchorEnabled = (0, _expValEquals.expValEquals)('platform_editor_controls_reliable_anchor', 'isEnabled', true);
909
+ var docDepForReliableAnchor = isReliableAnchorEnabled ? view.state.doc : undefined;
910
+
911
+ // Effect 1 (reliable-anchor ON): fires when non-doc deps change (e.g. blockCardWidth,
912
+ // anchorRectCache, macroInteractionUpdates, isTopLevelNodeValue, isLayoutColumn via
913
+ // calculatePositionOld) — always recalculates without the doc guard, so non-doc position
914
+ // changes are never incorrectly skipped.
904
915
  (0, _react.useEffect)(function () {
916
+ if (!isReliableAnchorEnabled) {
917
+ return;
918
+ }
905
919
  var cleanUpTransitionListener;
906
920
  if (nodeType === 'extension' || nodeType === 'embedCard') {
907
921
  var dom = view.dom.querySelector("[".concat((0, _domAttrName.getAnchorAttrName)(), "=\"").concat(anchorName, "\"]"));
@@ -917,13 +931,62 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
917
931
  }
918
932
  var calcPos = requestAnimationFrame(function () {
919
933
  setPositionStylesOld(calculatePositionOld());
934
+ hasCalculatedInitialPosition.current = true;
920
935
  });
921
936
  return function () {
922
937
  var _cleanUpTransitionLis;
923
938
  cancelAnimationFrame(calcPos);
924
939
  (_cleanUpTransitionLis = cleanUpTransitionListener) === null || _cleanUpTransitionLis === void 0 || _cleanUpTransitionLis();
925
940
  };
926
- }, [calculatePositionOld, view.dom, anchorName, nodeType]);
941
+ }, [isReliableAnchorEnabled, calculatePositionOld, view.dom, anchorName, nodeType]);
942
+
943
+ // Effect 2 (reliable-anchor ON): fires when the doc changes — carries the DOM-attribute
944
+ // guard to skip recalc when a drag handle is active (pure keystroke during drag).
945
+ // Effect (reliable-anchor OFF): single combined effect, original behaviour.
946
+ (0, _react.useEffect)(function () {
947
+ if (isReliableAnchorEnabled) {
948
+ // Doc-change only effect: apply the DOM-attribute guard.
949
+ // React's dep comparison already ensures this only runs when docDepForReliableAnchor
950
+ // (i.e. view.state.doc) changed, so no manual ref tracking is needed.
951
+ if (!hasCalculatedInitialPosition.current) {
952
+ return;
953
+ }
954
+ var _calcPos = requestAnimationFrame(function () {
955
+ var dom = view.dom.querySelector("[".concat((0, _domAttrName.getAnchorAttrName)(), "=\"").concat(anchorName, "\"]"));
956
+ if (dom !== null && dom !== void 0 && dom.hasAttribute(_consts2.ACTIVE_DRAG_HANDLE_ATTR)) {
957
+ return;
958
+ }
959
+ setPositionStylesOld(calculatePositionOld());
960
+ });
961
+ return function () {
962
+ cancelAnimationFrame(_calcPos);
963
+ };
964
+ }
965
+
966
+ // reliable-anchor OFF: original single-effect behaviour (no guard).
967
+ var cleanUpTransitionListener;
968
+ if (nodeType === 'extension' || nodeType === 'embedCard') {
969
+ var dom = view.dom.querySelector("[".concat((0, _domAttrName.getAnchorAttrName)(), "=\"").concat(anchorName, "\"]"));
970
+ if (!dom) {
971
+ return;
972
+ }
973
+ cleanUpTransitionListener = (0, _bindEventListener.bind)(dom, {
974
+ type: 'transitionend',
975
+ listener: function listener() {
976
+ setPositionStylesOld(calculatePositionOld());
977
+ }
978
+ });
979
+ }
980
+ var calcPos = requestAnimationFrame(function () {
981
+ setPositionStylesOld(calculatePositionOld());
982
+ hasCalculatedInitialPosition.current = true;
983
+ });
984
+ return function () {
985
+ var _cleanUpTransitionLis2;
986
+ cancelAnimationFrame(calcPos);
987
+ (_cleanUpTransitionLis2 = cleanUpTransitionListener) === null || _cleanUpTransitionLis2 === void 0 || _cleanUpTransitionLis2();
988
+ };
989
+ }, [isReliableAnchorEnabled, calculatePositionOld, view.dom, anchorName, nodeType, docDepForReliableAnchor]);
927
990
  (0, _react.useEffect)(function () {
928
991
  if (handleOptions !== null && handleOptions !== void 0 && handleOptions.isFocused && buttonRef.current) {
929
992
  var id = requestAnimationFrame(function () {
@@ -1192,9 +1255,15 @@ var DragHandleWithVisibility = exports.DragHandleWithVisibility = function DragH
1192
1255
  // when hovering anywhere over the column, regardless of which side of the layoutSection the
1193
1256
  // column is on. (The right-side remix button is a separate node decoration and is unaffected.)
1194
1257
  var isLayoutColumn = nodeType === 'layoutColumn';
1258
+
1259
+ // Skip the right-side controlSide restriction for non-top-level nodes. The restriction exists
1260
+ // to avoid showing the drag handle and remix button simultaneously — but remix only applies to
1261
+ // top-level nodes, so non-top-level nodes should never be restricted.
1262
+ // Gated behind platform_editor_controls_reliable_anchor.
1263
+ var skipControlSideRestriction = (0, _expValEquals.expValEquals)('platform_editor_controls_reliable_anchor', 'isEnabled', true) && isTopLevelNode === false;
1195
1264
  return (0, _react2.jsx)(_visibilityContainer.VisibilityContainer, {
1196
1265
  api: api,
1197
- controlSide: !isLayoutColumn && rightSideControlsEnabled ? 'left' : undefined,
1266
+ controlSide: !isLayoutColumn && !skipControlSideRestriction && rightSideControlsEnabled ? 'left' : undefined,
1198
1267
  forceVisibleOnMouseOut: (0, _expValEquals.expValEquals)('platform_editor_drag_handle_keyboard_a11y', 'isEnabled', true) && !!(handleOptions !== null && handleOptions !== void 0 && handleOptions.isFocused)
1199
1268
  }, (0, _react2.jsx)(DragHandle, {
1200
1269
  view: view,
@@ -423,6 +423,10 @@ var GlobalStylesWrapper = exports.GlobalStylesWrapper = function GlobalStylesWra
423
423
  exposure: true
424
424
  }) ? (0, _expValEquals.expValEquals)('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? extendHoverZoneReducedNext : extendHoverZoneReduced : undefined,
425
425
  // platform_editor_controls note: this allows drag handles to render on empty lines
426
- toolbarFlagsEnabled ? undefined : withInlineNodeStyle, withDeleteLinesStyleFix, withMediaSingleStyleFix, legacyBreakoutWideLayoutStyle, headingWithIndentationInLayoutStyleFix, (0, _experiments.editorExperiment)('advanced_layouts', true) ? blockCardWithoutLayout : undefined, withDividerInPanelStyleFix, withFormatInLayoutStyleFix, (0, _expValEquals.expValEquals)('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? withRelativePosStyleNext : withRelativePosStyle, topLevelNodeMarginStyles, (0, _expValEquals.expValEquals)('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? withAnchorNameZindexStyleNext : withAnchorNameZindexStyle, (0, _expValEquals.expValEquals)('platform_editor_native_anchor_with_dnd', 'isEnabled', true) && (0, _expValEquals.expValEquals)('advanced_layouts', 'isEnabled', true) ? layoutColumnExtendedHoverZone : layoutColumnWithoutHoverZone, shouldRenderAnchors && (isDragging ? dragAnchorStyles : dragHandlerAnchorStyles), (0, _expValEquals.expValEquals)('platform_editor_controls_reliable_anchor', 'isEnabled', true) ? staticControlsAnchorStyles : undefined]
426
+ toolbarFlagsEnabled ? undefined : withInlineNodeStyle, withDeleteLinesStyleFix, withMediaSingleStyleFix, legacyBreakoutWideLayoutStyle, headingWithIndentationInLayoutStyleFix, (0, _experiments.editorExperiment)('advanced_layouts', true) ? blockCardWithoutLayout : undefined, withDividerInPanelStyleFix, withFormatInLayoutStyleFix, (0, _expValEquals.expValEquals)('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? withRelativePosStyleNext : withRelativePosStyle, topLevelNodeMarginStyles, (0, _expValEquals.expValEquals)('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? withAnchorNameZindexStyleNext : withAnchorNameZindexStyle, (0, _expValEquals.expValEquals)('platform_editor_native_anchor_with_dnd', 'isEnabled', true) && (0, _expValEquals.expValEquals)('advanced_layouts', 'isEnabled', true) ? layoutColumnExtendedHoverZone : layoutColumnWithoutHoverZone, (0, _expValEquals.expValEquals)('platform_editor_controls_reliable_anchor', 'isEnabled', true) ?
427
+ // dragAnchorStyles sets anchor-name on ALL nodes during drag (needed for drop target positioning).
428
+ // staticControlsAnchorStyles handles anchor-name via node decorations for non-drag state.
429
+ // shouldRenderAnchors guards both: only apply anchor styles on browsers that support CSS anchor positioning.
430
+ shouldRenderAnchors ? isDragging ? dragAnchorStyles : staticControlsAnchorStyles : false : shouldRenderAnchors && (isDragging ? dragAnchorStyles : dragHandlerAnchorStyles)]
427
431
  });
428
432
  };
@@ -432,35 +432,37 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
432
432
  }
433
433
  }
434
434
  if (shouldRemoveHandle) {
435
- var _activeNode5, _activeNode6, _activeNode7;
435
+ var _activeNode5, _activeNode6;
436
436
  const oldHandle = findHandleDec(decorations, (_activeNode5 = activeNode) === null || _activeNode5 === void 0 ? void 0 : _activeNode5.pos, (_activeNode6 = activeNode) === null || _activeNode6 === void 0 ? void 0 : _activeNode6.pos);
437
437
  decorations = decorations.remove(oldHandle);
438
438
  // When removing the handle, also remove the anchor-marker node decorations
439
439
  // (data-active-drag-handle / data-active-quick-insert) so the DOM attributes
440
440
  // don't linger on nodes that are no longer active.
441
- if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && ((_activeNode7 = activeNode) === null || _activeNode7 === void 0 ? void 0 : _activeNode7.pos) !== undefined) {
442
- const oldActiveNodeDec = findActiveDragHandleNodeDec(decorations, activeNode.pos, activeNode.pos);
441
+ // Use full-range search (0..contentSize) rather than a point-range at activeNode.pos/rootPos:
442
+ // after a doc change (e.g. pressing Enter splits a node), DecorationSet.map() can shift the
443
+ // decoration to a different position, so a point-range search would miss it and leave a stale
444
+ // attribute on the wrong DOM node.
445
+ if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true)) {
446
+ const oldActiveNodeDec = findActiveDragHandleNodeDec(decorations, 0, newState.doc.content.size);
443
447
  decorations = decorations.remove(oldActiveNodeDec);
444
- if (activeNode.rootPos !== undefined) {
445
- const oldActiveQuickInsertDec = findActiveQuickInsertNodeDec(decorations, activeNode.rootPos, activeNode.rootPos);
446
- decorations = decorations.remove(oldActiveQuickInsertDec);
447
- }
448
+ const oldActiveQuickInsertDec = findActiveQuickInsertNodeDec(decorations, 0, newState.doc.content.size);
449
+ decorations = decorations.remove(oldActiveQuickInsertDec);
448
450
  }
449
451
  // platform_editor_controls note: enables quick insert
450
452
  if (flags.toolbarFlagsEnabled && quickInsertButtonEnabled) {
451
- var _activeNode8, _activeNode9;
452
- const oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode8 = activeNode) === null || _activeNode8 === void 0 ? void 0 : _activeNode8.rootPos, (_activeNode9 = activeNode) === null || _activeNode9 === void 0 ? void 0 : _activeNode9.rootPos);
453
+ var _activeNode7, _activeNode8;
454
+ const oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode7 = activeNode) === null || _activeNode7 === void 0 ? void 0 : _activeNode7.rootPos, (_activeNode8 = activeNode) === null || _activeNode8 === void 0 ? void 0 : _activeNode8.rootPos);
453
455
  decorations = decorations.remove(oldQuickInsertButton);
454
456
  for (const factory of nodeDecorationRegistry) {
455
- var _activeNode0, _activeNode1;
456
- const old = decorations.find((_activeNode0 = activeNode) === null || _activeNode0 === void 0 ? void 0 : _activeNode0.rootPos, (_activeNode1 = activeNode) === null || _activeNode1 === void 0 ? void 0 : _activeNode1.rootPos, spec => spec.type === factory.type);
457
+ var _activeNode9, _activeNode0;
458
+ const old = decorations.find((_activeNode9 = activeNode) === null || _activeNode9 === void 0 ? void 0 : _activeNode9.rootPos, (_activeNode0 = activeNode) === null || _activeNode0 === void 0 ? void 0 : _activeNode0.rootPos, spec => spec.type === factory.type);
457
459
  decorations = decorations.remove(old);
458
460
  }
459
461
  if (rightSideControlsEnabled && isViewMode && fg('confluence_remix_button_right_side_block_fg')) {
460
462
  for (const factory of nodeDecorationRegistry) {
461
463
  if (factory.showInViewMode) {
462
- var _activeNode10, _activeNode11;
463
- const old = decorations.find((_activeNode10 = activeNode) === null || _activeNode10 === void 0 ? void 0 : _activeNode10.rootPos, (_activeNode11 = activeNode) === null || _activeNode11 === void 0 ? void 0 : _activeNode11.rootPos, spec => spec.type === factory.type);
464
+ var _activeNode1, _activeNode10;
465
+ const old = decorations.find((_activeNode1 = activeNode) === null || _activeNode1 === void 0 ? void 0 : _activeNode1.rootPos, (_activeNode10 = activeNode) === null || _activeNode10 === void 0 ? void 0 : _activeNode10.rootPos, spec => spec.type === factory.type);
464
466
  decorations = decorations.remove(old);
465
467
  }
466
468
  }
@@ -469,8 +471,8 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
469
471
  } else if (api) {
470
472
  var _latestActiveNode5, _latestActiveNode10;
471
473
  if (shouldRecreateHandle && (!rightSideControlsEnabled || !isViewMode)) {
472
- var _activeNode12, _activeNode13, _latestActiveNode, _latestActiveNode2, _latestActiveNode3, _latestActiveNode4;
473
- const oldHandle = findHandleDec(decorations, (_activeNode12 = activeNode) === null || _activeNode12 === void 0 ? void 0 : _activeNode12.pos, (_activeNode13 = activeNode) === null || _activeNode13 === void 0 ? void 0 : _activeNode13.pos);
474
+ var _activeNode11, _activeNode12, _latestActiveNode, _latestActiveNode2, _latestActiveNode3, _latestActiveNode4;
475
+ const oldHandle = findHandleDec(decorations, (_activeNode11 = activeNode) === null || _activeNode11 === void 0 ? void 0 : _activeNode11.pos, (_activeNode12 = activeNode) === null || _activeNode12 === void 0 ? void 0 : _activeNode12.pos);
474
476
  decorations = decorations.remove(oldHandle);
475
477
  const handleDec = dragHandleDecoration({
476
478
  api,
@@ -484,33 +486,49 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
484
486
  editorState: newState
485
487
  });
486
488
  decorations = decorations.add(newState.doc, [handleDec]);
487
- if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true)) {
488
- // Recreate the drag handle node decoration when the edit-mode node itself changed
489
- // or its content was modified. Other triggers (editorSizeChanged, handleNeedsRedraw)
490
- // don't move the decoration DecorationSet.map() already remaps it correctly.
491
- const needsNodeDecUpdate = activeNodeChanged || isActiveNodeModified;
492
- if (needsNodeDecUpdate) {
493
- var _newState$doc$nodeAt;
494
- const nodeSize = (_newState$doc$nodeAt = newState.doc.nodeAt(latestActiveNode.pos)) === null || _newState$doc$nodeAt === void 0 ? void 0 : _newState$doc$nodeAt.nodeSize;
495
- if (nodeSize !== undefined) {
496
- var _activeNode14, _activeNode15;
497
- const oldActiveNodeDec = findActiveDragHandleNodeDec(decorations, (_activeNode14 = activeNode) === null || _activeNode14 === void 0 ? void 0 : _activeNode14.pos, (_activeNode15 = activeNode) === null || _activeNode15 === void 0 ? void 0 : _activeNode15.pos);
489
+ }
490
+ if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && latestActiveNode) {
491
+ // Recreate the drag handle node decoration when the active node changed,
492
+ // its content was modified, or the document changed (e.g. Enter splits a node).
493
+ // This runs independently of shouldRecreateHandle so that doc changes (like pressing
494
+ // Enter) correctly refresh the decoration even when the handle widget doesn't move.
495
+ // DecorationSet.map() can misplace the decoration after node splits/inserts.
496
+ const needsNodeDecUpdate = activeNodeChanged || isActiveNodeModified || tr.docChanged;
497
+ if (needsNodeDecUpdate) {
498
+ var _newState$doc$nodeAt;
499
+ const nodeSize = (_newState$doc$nodeAt = newState.doc.nodeAt(latestActiveNode.pos)) === null || _newState$doc$nodeAt === void 0 ? void 0 : _newState$doc$nodeAt.nodeSize;
500
+ if (nodeSize !== undefined) {
501
+ // Search the full doc range: after DecorationSet.map(), the decoration may have
502
+ // shifted away from activeNode?.pos (e.g. a node split moves it to the new paragraph).
503
+ const oldActiveNodeDec = findActiveDragHandleNodeDec(decorations, 0, newState.doc.content.size);
504
+ // Skip remove+add if the decoration is already at the correct position.
505
+ // On pure text keystrokes, DecorationSet.map() keeps the decoration correctly
506
+ // placed, so we avoid unnecessary churn on every character typed.
507
+ const dragHandleDecAlreadyCorrect = oldActiveNodeDec.length === 1 && oldActiveNodeDec[0].from === latestActiveNode.pos && oldActiveNodeDec[0].to === latestActiveNode.pos + nodeSize;
508
+ if (!dragHandleDecAlreadyCorrect) {
498
509
  decorations = decorations.remove(oldActiveNodeDec);
499
510
  decorations = decorations.add(newState.doc, [createActiveDragHandleNodeDecoration(latestActiveNode.pos, nodeSize)]);
500
511
  }
501
512
  }
513
+ }
502
514
 
503
- // The quick-insert decoration lives on the root node (rootPos), which can change
504
- // independently of the edit-mode node — e.g. when only editorSizeChanged fires but
505
- // rootActiveNodeChanged is also true. So we use a separate, broader guard that
506
- // includes rootActiveNodeChanged to avoid leaving the attribute on a stale root node.
507
- const needsQuickInsertDecUpdate = activeNodeChanged || isActiveNodeModified || rootActiveNodeChanged;
508
- if (needsQuickInsertDecUpdate && latestActiveNode.rootPos !== undefined) {
509
- var _newState$doc$nodeAt2;
510
- const rootNodeSize = (_newState$doc$nodeAt2 = newState.doc.nodeAt(latestActiveNode.rootPos)) === null || _newState$doc$nodeAt2 === void 0 ? void 0 : _newState$doc$nodeAt2.nodeSize;
511
- if (rootNodeSize !== undefined) {
512
- var _activeNode16, _activeNode17;
513
- const oldActiveQuickInsertDec = findActiveQuickInsertNodeDec(decorations, (_activeNode16 = activeNode) === null || _activeNode16 === void 0 ? void 0 : _activeNode16.rootPos, (_activeNode17 = activeNode) === null || _activeNode17 === void 0 ? void 0 : _activeNode17.rootPos);
515
+ // The quick-insert decoration lives on the root node (rootPos), which can change
516
+ // independently of the edit-mode node — e.g. when only editorSizeChanged fires but
517
+ // rootActiveNodeChanged is also true. So we use a separate, broader guard that
518
+ // includes rootActiveNodeChanged to avoid leaving the attribute on a stale root node.
519
+ // tr.docChanged is included for the same reason as needsNodeDecUpdate: DecorationSet.map()
520
+ // can misplace the decoration after a node split, so we must refresh it on every doc change.
521
+ const needsQuickInsertDecUpdate = activeNodeChanged || isActiveNodeModified || rootActiveNodeChanged || tr.docChanged;
522
+ if (needsQuickInsertDecUpdate && latestActiveNode.rootPos !== undefined) {
523
+ var _newState$doc$nodeAt2;
524
+ const rootNodeSize = (_newState$doc$nodeAt2 = newState.doc.nodeAt(latestActiveNode.rootPos)) === null || _newState$doc$nodeAt2 === void 0 ? void 0 : _newState$doc$nodeAt2.nodeSize;
525
+ if (rootNodeSize !== undefined) {
526
+ const oldActiveQuickInsertDec = findActiveQuickInsertNodeDec(decorations, 0, newState.doc.content.size);
527
+ // Skip remove+add if the decoration is already at the correct position.
528
+ // On pure text keystrokes, DecorationSet.map() keeps the decoration correctly
529
+ // placed, so we avoid unnecessary churn on every character typed.
530
+ const quickInsertDecAlreadyCorrect = oldActiveQuickInsertDec.length === 1 && oldActiveQuickInsertDec[0].from === latestActiveNode.rootPos && oldActiveQuickInsertDec[0].to === latestActiveNode.rootPos + rootNodeSize;
531
+ if (!quickInsertDecAlreadyCorrect) {
514
532
  decorations = decorations.remove(oldActiveQuickInsertDec);
515
533
  decorations = decorations.add(newState.doc, [createActiveQuickInsertNodeDecoration(latestActiveNode.rootPos, rootNodeSize)]);
516
534
  }
@@ -520,8 +538,8 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
520
538
  if (shouldRecreateQuickInsertButton && ((_latestActiveNode5 = latestActiveNode) === null || _latestActiveNode5 === void 0 ? void 0 : _latestActiveNode5.rootPos) !== undefined &&
521
539
  // platform_editor_controls note: enables quick insert
522
540
  flags.toolbarFlagsEnabled && quickInsertButtonEnabled && (!rightSideControlsEnabled || !isViewMode)) {
523
- var _activeNode18, _activeNode19, _latestActiveNode6, _latestActiveNode7, _latestActiveNode8, _latestActiveNode9, _latestActiveNode0, _latestActiveNode1;
524
- const oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode18 = activeNode) === null || _activeNode18 === void 0 ? void 0 : _activeNode18.rootPos, (_activeNode19 = activeNode) === null || _activeNode19 === void 0 ? void 0 : _activeNode19.rootPos);
541
+ var _activeNode13, _activeNode14, _latestActiveNode6, _latestActiveNode7, _latestActiveNode8, _latestActiveNode9, _latestActiveNode0, _latestActiveNode1;
542
+ const oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode13 = activeNode) === null || _activeNode13 === void 0 ? void 0 : _activeNode13.rootPos, (_activeNode14 = activeNode) === null || _activeNode14 === void 0 ? void 0 : _activeNode14.rootPos);
525
543
  decorations = decorations.remove(oldQuickInsertButton);
526
544
  const quickInsertButton = quickInsertButtonDecoration({
527
545
  api,
@@ -540,12 +558,16 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
540
558
  // Update quick insert node decoration when the quick insert button is recreated but
541
559
  // the drag handle was NOT recreated (shouldRecreateHandle was false). When
542
560
  // shouldRecreateHandle is true, the block above already handles this.
543
- if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && !shouldRecreateHandle && ((_latestActiveNode1 = latestActiveNode) === null || _latestActiveNode1 === void 0 ? void 0 : _latestActiveNode1.rootPos) !== undefined) {
544
- var _activeNode20, _newState$doc$nodeAt3;
545
- if (((_activeNode20 = activeNode) === null || _activeNode20 === void 0 ? void 0 : _activeNode20.rootPos) !== undefined) {
546
- const oldActiveQuickInsertDec = findActiveQuickInsertNodeDec(decorations, activeNode.rootPos, activeNode.rootPos);
547
- decorations = decorations.remove(oldActiveQuickInsertDec);
548
- }
561
+ // Also skip when the needsQuickInsertDecUpdate block above already handled the update
562
+ // (i.e. activeNodeChanged || isActiveNodeModified || rootActiveNodeChanged || tr.docChanged)
563
+ // running both would remove and re-add a duplicate decoration.
564
+ if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && !shouldRecreateHandle && !(activeNodeChanged || isActiveNodeModified || rootActiveNodeChanged || tr.docChanged) && ((_latestActiveNode1 = latestActiveNode) === null || _latestActiveNode1 === void 0 ? void 0 : _latestActiveNode1.rootPos) !== undefined) {
565
+ var _newState$doc$nodeAt3;
566
+ // Use full-range search (0..contentSize) rather than a point-range at activeNode.rootPos:
567
+ // after a doc change, DecorationSet.map() can shift the decoration to a different position,
568
+ // so a point-range search would miss it and leave a stale attribute on the wrong DOM node.
569
+ const oldActiveQuickInsertDec = findActiveQuickInsertNodeDec(decorations, 0, newState.doc.content.size);
570
+ decorations = decorations.remove(oldActiveQuickInsertDec);
549
571
  const rootNodeSize = (_newState$doc$nodeAt3 = newState.doc.nodeAt(latestActiveNode.rootPos)) === null || _newState$doc$nodeAt3 === void 0 ? void 0 : _newState$doc$nodeAt3.nodeSize;
550
572
  if (rootNodeSize !== undefined) {
551
573
  decorations = decorations.add(newState.doc, [createActiveQuickInsertNodeDecoration(latestActiveNode.rootPos, rootNodeSize)]);
@@ -553,7 +575,7 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
553
575
  }
554
576
  if (rightSideControlsEnabled) {
555
577
  for (const factory of nodeDecorationRegistry) {
556
- var _activeNode21, _activeNode22;
578
+ var _activeNode15, _activeNode16;
557
579
  if (!latestActiveNode || latestActiveNode.rootPos === undefined) {
558
580
  continue;
559
581
  }
@@ -566,7 +588,7 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
566
588
  rootAnchorName: latestActiveNode.rootAnchorName,
567
589
  rootNodeType: latestActiveNode.rootNodeType
568
590
  };
569
- const old = decorations.find((_activeNode21 = activeNode) === null || _activeNode21 === void 0 ? void 0 : _activeNode21.rootPos, (_activeNode22 = activeNode) === null || _activeNode22 === void 0 ? void 0 : _activeNode22.rootPos, spec => spec.type === factory.type);
591
+ const old = decorations.find((_activeNode15 = activeNode) === null || _activeNode15 === void 0 ? void 0 : _activeNode15.rootPos, (_activeNode16 = activeNode) === null || _activeNode16 === void 0 ? void 0 : _activeNode16.rootPos, spec => spec.type === factory.type);
570
592
  decorations = decorations.remove(old);
571
593
 
572
594
  // determines whether to show the decorations, see malleableUiPlugin.tsx
@@ -2,7 +2,6 @@ import { INSERTED_TRAILING_PARAGRAPH_TO_LAST_NODE_META } from '@atlaskit/editor-
2
2
  import { expandToBlockRange, isMultiBlockRange } from '@atlaskit/editor-common/selection';
3
3
  import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
4
4
  import { getTableSelectionClosesToPos } from '@atlaskit/editor-tables/utils';
5
- import { fg } from '@atlaskit/platform-feature-flags';
6
5
  import { getBlockControlsMeta, key } from '../main';
7
6
  import { newGetSelection } from './getSelection';
8
7
  export const getMultiSelectionIfPosInside = (api, pos, tr) => {
@@ -95,7 +94,7 @@ export const mapPreservedSelection = (selection, tr) => {
95
94
  // When lastNodeMustBeParagraph inserts a trailing paragraph exactly at the end boundary
96
95
  // of a preserved block selection, keep the mapped end position on the left side of the
97
96
  // inserted paragraph so it is not included in the preserved selection.
98
- const shouldTrimTrailingParagraph = fg('platform_editor_block_menu_jira_patch_2') && tr.getMeta(INSERTED_TRAILING_PARAGRAPH_TO_LAST_NODE_META) === true;
97
+ const shouldTrimTrailingParagraph = tr.getMeta(INSERTED_TRAILING_PARAGRAPH_TO_LAST_NODE_META) === true;
99
98
  const to = shouldTrimTrailingParagraph ? mapping.map(selection.to, -1) : mapping.map(selection.to);
100
99
  const isSelectionEmpty = from === to;
101
100
  const wasSelectionEmpty = selection.from === selection.to;
@@ -395,6 +395,10 @@ export const DragHandle = ({
395
395
  const [positionStylesOld, setPositionStylesOld] = useState({
396
396
  display: 'none'
397
397
  });
398
+ // Tracks whether the initial position calculation has been performed at least once.
399
+ // The reliable-anchor early-return optimisation must not fire before the first calculation,
400
+ // otherwise positionStylesOld stays as { display: 'none' } and the handle is never shown.
401
+ const hasCalculatedInitialPosition = useRef(false);
398
402
  const [isFocused, setIsFocused] = useState(Boolean(handleOptions === null || handleOptions === void 0 ? void 0 : handleOptions.isFocused));
399
403
  const {
400
404
  macroInteractionUpdates,
@@ -881,7 +885,17 @@ export const DragHandle = ({
881
885
  ...height
882
886
  };
883
887
  }, [anchorName, getPos, view, nodeType, blockCardWidth, macroInteractionUpdates, anchorRectCache, isTopLevelNodeValue, isLayoutColumn]);
888
+ const isReliableAnchorEnabled = expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true);
889
+ const docDepForReliableAnchor = isReliableAnchorEnabled ? view.state.doc : undefined;
890
+
891
+ // Effect 1 (reliable-anchor ON): fires when non-doc deps change (e.g. blockCardWidth,
892
+ // anchorRectCache, macroInteractionUpdates, isTopLevelNodeValue, isLayoutColumn via
893
+ // calculatePositionOld) — always recalculates without the doc guard, so non-doc position
894
+ // changes are never incorrectly skipped.
884
895
  useEffect(() => {
896
+ if (!isReliableAnchorEnabled) {
897
+ return;
898
+ }
885
899
  let cleanUpTransitionListener;
886
900
  if (nodeType === 'extension' || nodeType === 'embedCard') {
887
901
  const dom = view.dom.querySelector(`[${getAnchorAttrName()}="${anchorName}"]`);
@@ -897,13 +911,62 @@ export const DragHandle = ({
897
911
  }
898
912
  const calcPos = requestAnimationFrame(() => {
899
913
  setPositionStylesOld(calculatePositionOld());
914
+ hasCalculatedInitialPosition.current = true;
900
915
  });
901
916
  return () => {
902
917
  var _cleanUpTransitionLis;
903
918
  cancelAnimationFrame(calcPos);
904
919
  (_cleanUpTransitionLis = cleanUpTransitionListener) === null || _cleanUpTransitionLis === void 0 ? void 0 : _cleanUpTransitionLis();
905
920
  };
906
- }, [calculatePositionOld, view.dom, anchorName, nodeType]);
921
+ }, [isReliableAnchorEnabled, calculatePositionOld, view.dom, anchorName, nodeType]);
922
+
923
+ // Effect 2 (reliable-anchor ON): fires when the doc changes — carries the DOM-attribute
924
+ // guard to skip recalc when a drag handle is active (pure keystroke during drag).
925
+ // Effect (reliable-anchor OFF): single combined effect, original behaviour.
926
+ useEffect(() => {
927
+ if (isReliableAnchorEnabled) {
928
+ // Doc-change only effect: apply the DOM-attribute guard.
929
+ // React's dep comparison already ensures this only runs when docDepForReliableAnchor
930
+ // (i.e. view.state.doc) changed, so no manual ref tracking is needed.
931
+ if (!hasCalculatedInitialPosition.current) {
932
+ return;
933
+ }
934
+ const calcPos = requestAnimationFrame(() => {
935
+ const dom = view.dom.querySelector(`[${getAnchorAttrName()}="${anchorName}"]`);
936
+ if (dom !== null && dom !== void 0 && dom.hasAttribute(ACTIVE_DRAG_HANDLE_ATTR)) {
937
+ return;
938
+ }
939
+ setPositionStylesOld(calculatePositionOld());
940
+ });
941
+ return () => {
942
+ cancelAnimationFrame(calcPos);
943
+ };
944
+ }
945
+
946
+ // reliable-anchor OFF: original single-effect behaviour (no guard).
947
+ let cleanUpTransitionListener;
948
+ if (nodeType === 'extension' || nodeType === 'embedCard') {
949
+ const dom = view.dom.querySelector(`[${getAnchorAttrName()}="${anchorName}"]`);
950
+ if (!dom) {
951
+ return;
952
+ }
953
+ cleanUpTransitionListener = bind(dom, {
954
+ type: 'transitionend',
955
+ listener: () => {
956
+ setPositionStylesOld(calculatePositionOld());
957
+ }
958
+ });
959
+ }
960
+ const calcPos = requestAnimationFrame(() => {
961
+ setPositionStylesOld(calculatePositionOld());
962
+ hasCalculatedInitialPosition.current = true;
963
+ });
964
+ return () => {
965
+ var _cleanUpTransitionLis2;
966
+ cancelAnimationFrame(calcPos);
967
+ (_cleanUpTransitionLis2 = cleanUpTransitionListener) === null || _cleanUpTransitionLis2 === void 0 ? void 0 : _cleanUpTransitionLis2();
968
+ };
969
+ }, [isReliableAnchorEnabled, calculatePositionOld, view.dom, anchorName, nodeType, docDepForReliableAnchor]);
907
970
  useEffect(() => {
908
971
  if (handleOptions !== null && handleOptions !== void 0 && handleOptions.isFocused && buttonRef.current) {
909
972
  const id = requestAnimationFrame(() => {
@@ -1165,9 +1228,15 @@ export const DragHandleWithVisibility = ({
1165
1228
  // when hovering anywhere over the column, regardless of which side of the layoutSection the
1166
1229
  // column is on. (The right-side remix button is a separate node decoration and is unaffected.)
1167
1230
  const isLayoutColumn = nodeType === 'layoutColumn';
1231
+
1232
+ // Skip the right-side controlSide restriction for non-top-level nodes. The restriction exists
1233
+ // to avoid showing the drag handle and remix button simultaneously — but remix only applies to
1234
+ // top-level nodes, so non-top-level nodes should never be restricted.
1235
+ // Gated behind platform_editor_controls_reliable_anchor.
1236
+ const skipControlSideRestriction = expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && isTopLevelNode === false;
1168
1237
  return jsx(VisibilityContainer, {
1169
1238
  api: api,
1170
- controlSide: !isLayoutColumn && rightSideControlsEnabled ? 'left' : undefined,
1239
+ controlSide: !isLayoutColumn && !skipControlSideRestriction && rightSideControlsEnabled ? 'left' : undefined,
1171
1240
  forceVisibleOnMouseOut: expValEquals('platform_editor_drag_handle_keyboard_a11y', 'isEnabled', true) && !!(handleOptions !== null && handleOptions !== void 0 && handleOptions.isFocused)
1172
1241
  }, jsx(DragHandle, {
1173
1242
  view: view,
@@ -514,6 +514,10 @@ export const GlobalStylesWrapper = ({
514
514
  exposure: true
515
515
  }) ? expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? extendHoverZoneReducedNext : extendHoverZoneReduced : undefined,
516
516
  // platform_editor_controls note: this allows drag handles to render on empty lines
517
- toolbarFlagsEnabled ? undefined : withInlineNodeStyle, withDeleteLinesStyleFix, withMediaSingleStyleFix, legacyBreakoutWideLayoutStyle, headingWithIndentationInLayoutStyleFix, editorExperiment('advanced_layouts', true) ? blockCardWithoutLayout : undefined, withDividerInPanelStyleFix, withFormatInLayoutStyleFix, expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? withRelativePosStyleNext : withRelativePosStyle, topLevelNodeMarginStyles, expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? withAnchorNameZindexStyleNext : withAnchorNameZindexStyle, expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) && expValEquals('advanced_layouts', 'isEnabled', true) ? layoutColumnExtendedHoverZone : layoutColumnWithoutHoverZone, shouldRenderAnchors && (isDragging ? dragAnchorStyles : dragHandlerAnchorStyles), expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) ? staticControlsAnchorStyles : undefined]
517
+ toolbarFlagsEnabled ? undefined : withInlineNodeStyle, withDeleteLinesStyleFix, withMediaSingleStyleFix, legacyBreakoutWideLayoutStyle, headingWithIndentationInLayoutStyleFix, editorExperiment('advanced_layouts', true) ? blockCardWithoutLayout : undefined, withDividerInPanelStyleFix, withFormatInLayoutStyleFix, expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? withRelativePosStyleNext : withRelativePosStyle, topLevelNodeMarginStyles, expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? withAnchorNameZindexStyleNext : withAnchorNameZindexStyle, expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) && expValEquals('advanced_layouts', 'isEnabled', true) ? layoutColumnExtendedHoverZone : layoutColumnWithoutHoverZone, expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) ?
518
+ // dragAnchorStyles sets anchor-name on ALL nodes during drag (needed for drop target positioning).
519
+ // staticControlsAnchorStyles handles anchor-name via node decorations for non-drag state.
520
+ // shouldRenderAnchors guards both: only apply anchor styles on browsers that support CSS anchor positioning.
521
+ shouldRenderAnchors ? isDragging ? dragAnchorStyles : staticControlsAnchorStyles : false : shouldRenderAnchors && (isDragging ? dragAnchorStyles : dragHandlerAnchorStyles)]
518
522
  });
519
523
  };
@@ -435,32 +435,34 @@ var _apply = function apply(api, formatMessage, tr, currentState, newState, flag
435
435
  }
436
436
  }
437
437
  if (shouldRemoveHandle) {
438
- var _activeNode5, _activeNode6, _activeNode7;
438
+ var _activeNode5, _activeNode6;
439
439
  var oldHandle = findHandleDec(decorations, (_activeNode5 = activeNode) === null || _activeNode5 === void 0 ? void 0 : _activeNode5.pos, (_activeNode6 = activeNode) === null || _activeNode6 === void 0 ? void 0 : _activeNode6.pos);
440
440
  decorations = decorations.remove(oldHandle);
441
441
  // When removing the handle, also remove the anchor-marker node decorations
442
442
  // (data-active-drag-handle / data-active-quick-insert) so the DOM attributes
443
443
  // don't linger on nodes that are no longer active.
444
- if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && ((_activeNode7 = activeNode) === null || _activeNode7 === void 0 ? void 0 : _activeNode7.pos) !== undefined) {
445
- var oldActiveNodeDec = findActiveDragHandleNodeDec(decorations, activeNode.pos, activeNode.pos);
444
+ // Use full-range search (0..contentSize) rather than a point-range at activeNode.pos/rootPos:
445
+ // after a doc change (e.g. pressing Enter splits a node), DecorationSet.map() can shift the
446
+ // decoration to a different position, so a point-range search would miss it and leave a stale
447
+ // attribute on the wrong DOM node.
448
+ if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true)) {
449
+ var oldActiveNodeDec = findActiveDragHandleNodeDec(decorations, 0, newState.doc.content.size);
446
450
  decorations = decorations.remove(oldActiveNodeDec);
447
- if (activeNode.rootPos !== undefined) {
448
- var oldActiveQuickInsertDec = findActiveQuickInsertNodeDec(decorations, activeNode.rootPos, activeNode.rootPos);
449
- decorations = decorations.remove(oldActiveQuickInsertDec);
450
- }
451
+ var oldActiveQuickInsertDec = findActiveQuickInsertNodeDec(decorations, 0, newState.doc.content.size);
452
+ decorations = decorations.remove(oldActiveQuickInsertDec);
451
453
  }
452
454
  // platform_editor_controls note: enables quick insert
453
455
  if (flags.toolbarFlagsEnabled && quickInsertButtonEnabled) {
454
- var _activeNode8, _activeNode9;
455
- var oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode8 = activeNode) === null || _activeNode8 === void 0 ? void 0 : _activeNode8.rootPos, (_activeNode9 = activeNode) === null || _activeNode9 === void 0 ? void 0 : _activeNode9.rootPos);
456
+ var _activeNode7, _activeNode8;
457
+ var oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode7 = activeNode) === null || _activeNode7 === void 0 ? void 0 : _activeNode7.rootPos, (_activeNode8 = activeNode) === null || _activeNode8 === void 0 ? void 0 : _activeNode8.rootPos);
456
458
  decorations = decorations.remove(oldQuickInsertButton);
457
459
  var _iterator = _createForOfIteratorHelper(nodeDecorationRegistry),
458
460
  _step;
459
461
  try {
460
462
  var _loop2 = function _loop2() {
461
- var _activeNode10, _activeNode11;
463
+ var _activeNode1, _activeNode10;
462
464
  var factory = _step.value;
463
- var old = decorations.find((_activeNode10 = activeNode) === null || _activeNode10 === void 0 ? void 0 : _activeNode10.rootPos, (_activeNode11 = activeNode) === null || _activeNode11 === void 0 ? void 0 : _activeNode11.rootPos, function (spec) {
465
+ var old = decorations.find((_activeNode1 = activeNode) === null || _activeNode1 === void 0 ? void 0 : _activeNode1.rootPos, (_activeNode10 = activeNode) === null || _activeNode10 === void 0 ? void 0 : _activeNode10.rootPos, function (spec) {
464
466
  return spec.type === factory.type;
465
467
  });
466
468
  decorations = decorations.remove(old);
@@ -480,8 +482,8 @@ var _apply = function apply(api, formatMessage, tr, currentState, newState, flag
480
482
  var _loop = function _loop() {
481
483
  var factory = _step2.value;
482
484
  if (factory.showInViewMode) {
483
- var _activeNode0, _activeNode1;
484
- var old = decorations.find((_activeNode0 = activeNode) === null || _activeNode0 === void 0 ? void 0 : _activeNode0.rootPos, (_activeNode1 = activeNode) === null || _activeNode1 === void 0 ? void 0 : _activeNode1.rootPos, function (spec) {
485
+ var _activeNode9, _activeNode0;
486
+ var old = decorations.find((_activeNode9 = activeNode) === null || _activeNode9 === void 0 ? void 0 : _activeNode9.rootPos, (_activeNode0 = activeNode) === null || _activeNode0 === void 0 ? void 0 : _activeNode0.rootPos, function (spec) {
485
487
  return spec.type === factory.type;
486
488
  });
487
489
  decorations = decorations.remove(old);
@@ -500,8 +502,8 @@ var _apply = function apply(api, formatMessage, tr, currentState, newState, flag
500
502
  } else if (api) {
501
503
  var _latestActiveNode5, _latestActiveNode10;
502
504
  if (shouldRecreateHandle && (!rightSideControlsEnabled || !isViewMode)) {
503
- var _activeNode12, _activeNode13, _latestActiveNode, _latestActiveNode2, _latestActiveNode3, _latestActiveNode4;
504
- var _oldHandle = findHandleDec(decorations, (_activeNode12 = activeNode) === null || _activeNode12 === void 0 ? void 0 : _activeNode12.pos, (_activeNode13 = activeNode) === null || _activeNode13 === void 0 ? void 0 : _activeNode13.pos);
505
+ var _activeNode11, _activeNode12, _latestActiveNode, _latestActiveNode2, _latestActiveNode3, _latestActiveNode4;
506
+ var _oldHandle = findHandleDec(decorations, (_activeNode11 = activeNode) === null || _activeNode11 === void 0 ? void 0 : _activeNode11.pos, (_activeNode12 = activeNode) === null || _activeNode12 === void 0 ? void 0 : _activeNode12.pos);
505
507
  decorations = decorations.remove(_oldHandle);
506
508
  var handleDec = dragHandleDecoration({
507
509
  api: api,
@@ -515,33 +517,49 @@ var _apply = function apply(api, formatMessage, tr, currentState, newState, flag
515
517
  editorState: newState
516
518
  });
517
519
  decorations = decorations.add(newState.doc, [handleDec]);
518
- if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true)) {
519
- // Recreate the drag handle node decoration when the edit-mode node itself changed
520
- // or its content was modified. Other triggers (editorSizeChanged, handleNeedsRedraw)
521
- // don't move the decoration DecorationSet.map() already remaps it correctly.
522
- var needsNodeDecUpdate = activeNodeChanged || isActiveNodeModified;
523
- if (needsNodeDecUpdate) {
524
- var _newState$doc$nodeAt;
525
- var nodeSize = (_newState$doc$nodeAt = newState.doc.nodeAt(latestActiveNode.pos)) === null || _newState$doc$nodeAt === void 0 ? void 0 : _newState$doc$nodeAt.nodeSize;
526
- if (nodeSize !== undefined) {
527
- var _activeNode14, _activeNode15;
528
- var _oldActiveNodeDec = findActiveDragHandleNodeDec(decorations, (_activeNode14 = activeNode) === null || _activeNode14 === void 0 ? void 0 : _activeNode14.pos, (_activeNode15 = activeNode) === null || _activeNode15 === void 0 ? void 0 : _activeNode15.pos);
520
+ }
521
+ if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && latestActiveNode) {
522
+ // Recreate the drag handle node decoration when the active node changed,
523
+ // its content was modified, or the document changed (e.g. Enter splits a node).
524
+ // This runs independently of shouldRecreateHandle so that doc changes (like pressing
525
+ // Enter) correctly refresh the decoration even when the handle widget doesn't move.
526
+ // DecorationSet.map() can misplace the decoration after node splits/inserts.
527
+ var needsNodeDecUpdate = activeNodeChanged || isActiveNodeModified || tr.docChanged;
528
+ if (needsNodeDecUpdate) {
529
+ var _newState$doc$nodeAt;
530
+ var nodeSize = (_newState$doc$nodeAt = newState.doc.nodeAt(latestActiveNode.pos)) === null || _newState$doc$nodeAt === void 0 ? void 0 : _newState$doc$nodeAt.nodeSize;
531
+ if (nodeSize !== undefined) {
532
+ // Search the full doc range: after DecorationSet.map(), the decoration may have
533
+ // shifted away from activeNode?.pos (e.g. a node split moves it to the new paragraph).
534
+ var _oldActiveNodeDec = findActiveDragHandleNodeDec(decorations, 0, newState.doc.content.size);
535
+ // Skip remove+add if the decoration is already at the correct position.
536
+ // On pure text keystrokes, DecorationSet.map() keeps the decoration correctly
537
+ // placed, so we avoid unnecessary churn on every character typed.
538
+ var dragHandleDecAlreadyCorrect = _oldActiveNodeDec.length === 1 && _oldActiveNodeDec[0].from === latestActiveNode.pos && _oldActiveNodeDec[0].to === latestActiveNode.pos + nodeSize;
539
+ if (!dragHandleDecAlreadyCorrect) {
529
540
  decorations = decorations.remove(_oldActiveNodeDec);
530
541
  decorations = decorations.add(newState.doc, [createActiveDragHandleNodeDecoration(latestActiveNode.pos, nodeSize)]);
531
542
  }
532
543
  }
544
+ }
533
545
 
534
- // The quick-insert decoration lives on the root node (rootPos), which can change
535
- // independently of the edit-mode node — e.g. when only editorSizeChanged fires but
536
- // rootActiveNodeChanged is also true. So we use a separate, broader guard that
537
- // includes rootActiveNodeChanged to avoid leaving the attribute on a stale root node.
538
- var needsQuickInsertDecUpdate = activeNodeChanged || isActiveNodeModified || rootActiveNodeChanged;
539
- if (needsQuickInsertDecUpdate && latestActiveNode.rootPos !== undefined) {
540
- var _newState$doc$nodeAt2;
541
- var rootNodeSize = (_newState$doc$nodeAt2 = newState.doc.nodeAt(latestActiveNode.rootPos)) === null || _newState$doc$nodeAt2 === void 0 ? void 0 : _newState$doc$nodeAt2.nodeSize;
542
- if (rootNodeSize !== undefined) {
543
- var _activeNode16, _activeNode17;
544
- var _oldActiveQuickInsertDec = findActiveQuickInsertNodeDec(decorations, (_activeNode16 = activeNode) === null || _activeNode16 === void 0 ? void 0 : _activeNode16.rootPos, (_activeNode17 = activeNode) === null || _activeNode17 === void 0 ? void 0 : _activeNode17.rootPos);
546
+ // The quick-insert decoration lives on the root node (rootPos), which can change
547
+ // independently of the edit-mode node — e.g. when only editorSizeChanged fires but
548
+ // rootActiveNodeChanged is also true. So we use a separate, broader guard that
549
+ // includes rootActiveNodeChanged to avoid leaving the attribute on a stale root node.
550
+ // tr.docChanged is included for the same reason as needsNodeDecUpdate: DecorationSet.map()
551
+ // can misplace the decoration after a node split, so we must refresh it on every doc change.
552
+ var needsQuickInsertDecUpdate = activeNodeChanged || isActiveNodeModified || rootActiveNodeChanged || tr.docChanged;
553
+ if (needsQuickInsertDecUpdate && latestActiveNode.rootPos !== undefined) {
554
+ var _newState$doc$nodeAt2;
555
+ var rootNodeSize = (_newState$doc$nodeAt2 = newState.doc.nodeAt(latestActiveNode.rootPos)) === null || _newState$doc$nodeAt2 === void 0 ? void 0 : _newState$doc$nodeAt2.nodeSize;
556
+ if (rootNodeSize !== undefined) {
557
+ var _oldActiveQuickInsertDec = findActiveQuickInsertNodeDec(decorations, 0, newState.doc.content.size);
558
+ // Skip remove+add if the decoration is already at the correct position.
559
+ // On pure text keystrokes, DecorationSet.map() keeps the decoration correctly
560
+ // placed, so we avoid unnecessary churn on every character typed.
561
+ var quickInsertDecAlreadyCorrect = _oldActiveQuickInsertDec.length === 1 && _oldActiveQuickInsertDec[0].from === latestActiveNode.rootPos && _oldActiveQuickInsertDec[0].to === latestActiveNode.rootPos + rootNodeSize;
562
+ if (!quickInsertDecAlreadyCorrect) {
545
563
  decorations = decorations.remove(_oldActiveQuickInsertDec);
546
564
  decorations = decorations.add(newState.doc, [createActiveQuickInsertNodeDecoration(latestActiveNode.rootPos, rootNodeSize)]);
547
565
  }
@@ -551,8 +569,8 @@ var _apply = function apply(api, formatMessage, tr, currentState, newState, flag
551
569
  if (shouldRecreateQuickInsertButton && ((_latestActiveNode5 = latestActiveNode) === null || _latestActiveNode5 === void 0 ? void 0 : _latestActiveNode5.rootPos) !== undefined &&
552
570
  // platform_editor_controls note: enables quick insert
553
571
  flags.toolbarFlagsEnabled && quickInsertButtonEnabled && (!rightSideControlsEnabled || !isViewMode)) {
554
- var _activeNode18, _activeNode19, _latestActiveNode6, _latestActiveNode7, _latestActiveNode8, _latestActiveNode9, _latestActiveNode0, _latestActiveNode1;
555
- var _oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode18 = activeNode) === null || _activeNode18 === void 0 ? void 0 : _activeNode18.rootPos, (_activeNode19 = activeNode) === null || _activeNode19 === void 0 ? void 0 : _activeNode19.rootPos);
572
+ var _activeNode13, _activeNode14, _latestActiveNode6, _latestActiveNode7, _latestActiveNode8, _latestActiveNode9, _latestActiveNode0, _latestActiveNode1;
573
+ var _oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode13 = activeNode) === null || _activeNode13 === void 0 ? void 0 : _activeNode13.rootPos, (_activeNode14 = activeNode) === null || _activeNode14 === void 0 ? void 0 : _activeNode14.rootPos);
556
574
  decorations = decorations.remove(_oldQuickInsertButton);
557
575
  var quickInsertButton = quickInsertButtonDecoration({
558
576
  api: api,
@@ -571,12 +589,16 @@ var _apply = function apply(api, formatMessage, tr, currentState, newState, flag
571
589
  // Update quick insert node decoration when the quick insert button is recreated but
572
590
  // the drag handle was NOT recreated (shouldRecreateHandle was false). When
573
591
  // shouldRecreateHandle is true, the block above already handles this.
574
- if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && !shouldRecreateHandle && ((_latestActiveNode1 = latestActiveNode) === null || _latestActiveNode1 === void 0 ? void 0 : _latestActiveNode1.rootPos) !== undefined) {
575
- var _activeNode20, _newState$doc$nodeAt3;
576
- if (((_activeNode20 = activeNode) === null || _activeNode20 === void 0 ? void 0 : _activeNode20.rootPos) !== undefined) {
577
- var _oldActiveQuickInsertDec2 = findActiveQuickInsertNodeDec(decorations, activeNode.rootPos, activeNode.rootPos);
578
- decorations = decorations.remove(_oldActiveQuickInsertDec2);
579
- }
592
+ // Also skip when the needsQuickInsertDecUpdate block above already handled the update
593
+ // (i.e. activeNodeChanged || isActiveNodeModified || rootActiveNodeChanged || tr.docChanged)
594
+ // running both would remove and re-add a duplicate decoration.
595
+ if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && !shouldRecreateHandle && !(activeNodeChanged || isActiveNodeModified || rootActiveNodeChanged || tr.docChanged) && ((_latestActiveNode1 = latestActiveNode) === null || _latestActiveNode1 === void 0 ? void 0 : _latestActiveNode1.rootPos) !== undefined) {
596
+ var _newState$doc$nodeAt3;
597
+ // Use full-range search (0..contentSize) rather than a point-range at activeNode.rootPos:
598
+ // after a doc change, DecorationSet.map() can shift the decoration to a different position,
599
+ // so a point-range search would miss it and leave a stale attribute on the wrong DOM node.
600
+ var _oldActiveQuickInsertDec2 = findActiveQuickInsertNodeDec(decorations, 0, newState.doc.content.size);
601
+ decorations = decorations.remove(_oldActiveQuickInsertDec2);
580
602
  var _rootNodeSize = (_newState$doc$nodeAt3 = newState.doc.nodeAt(latestActiveNode.rootPos)) === null || _newState$doc$nodeAt3 === void 0 ? void 0 : _newState$doc$nodeAt3.nodeSize;
581
603
  if (_rootNodeSize !== undefined) {
582
604
  decorations = decorations.add(newState.doc, [createActiveQuickInsertNodeDecoration(latestActiveNode.rootPos, _rootNodeSize)]);
@@ -587,7 +609,7 @@ var _apply = function apply(api, formatMessage, tr, currentState, newState, flag
587
609
  _step3;
588
610
  try {
589
611
  var _loop3 = function _loop3() {
590
- var _activeNode21, _activeNode22;
612
+ var _activeNode15, _activeNode16;
591
613
  var factory = _step3.value;
592
614
  if (!latestActiveNode || latestActiveNode.rootPos === undefined) {
593
615
  return 0; // continue
@@ -601,7 +623,7 @@ var _apply = function apply(api, formatMessage, tr, currentState, newState, flag
601
623
  rootAnchorName: latestActiveNode.rootAnchorName,
602
624
  rootNodeType: latestActiveNode.rootNodeType
603
625
  };
604
- var old = decorations.find((_activeNode21 = activeNode) === null || _activeNode21 === void 0 ? void 0 : _activeNode21.rootPos, (_activeNode22 = activeNode) === null || _activeNode22 === void 0 ? void 0 : _activeNode22.rootPos, function (spec) {
626
+ var old = decorations.find((_activeNode15 = activeNode) === null || _activeNode15 === void 0 ? void 0 : _activeNode15.rootPos, (_activeNode16 = activeNode) === null || _activeNode16 === void 0 ? void 0 : _activeNode16.rootPos, function (spec) {
605
627
  return spec.type === factory.type;
606
628
  });
607
629
  decorations = decorations.remove(old);
@@ -2,7 +2,6 @@ import { INSERTED_TRAILING_PARAGRAPH_TO_LAST_NODE_META } from '@atlaskit/editor-
2
2
  import { expandToBlockRange, isMultiBlockRange } from '@atlaskit/editor-common/selection';
3
3
  import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
4
4
  import { getTableSelectionClosesToPos } from '@atlaskit/editor-tables/utils';
5
- import { fg } from '@atlaskit/platform-feature-flags';
6
5
  import { getBlockControlsMeta, key } from '../main';
7
6
  import { newGetSelection } from './getSelection';
8
7
  export var getMultiSelectionIfPosInside = function getMultiSelectionIfPosInside(api, pos, tr) {
@@ -93,7 +92,7 @@ export var mapPreservedSelection = function mapPreservedSelection(selection, tr)
93
92
  // When lastNodeMustBeParagraph inserts a trailing paragraph exactly at the end boundary
94
93
  // of a preserved block selection, keep the mapped end position on the left side of the
95
94
  // inserted paragraph so it is not included in the preserved selection.
96
- var shouldTrimTrailingParagraph = fg('platform_editor_block_menu_jira_patch_2') && tr.getMeta(INSERTED_TRAILING_PARAGRAPH_TO_LAST_NODE_META) === true;
95
+ var shouldTrimTrailingParagraph = tr.getMeta(INSERTED_TRAILING_PARAGRAPH_TO_LAST_NODE_META) === true;
97
96
  var to = shouldTrimTrailingParagraph ? mapping.map(selection.to, -1) : mapping.map(selection.to);
98
97
  var isSelectionEmpty = from === to;
99
98
  var wasSelectionEmpty = selection.from === selection.to;
@@ -411,6 +411,10 @@ export var DragHandle = function DragHandle(_ref) {
411
411
  _useState8 = _slicedToArray(_useState7, 2),
412
412
  positionStylesOld = _useState8[0],
413
413
  setPositionStylesOld = _useState8[1];
414
+ // Tracks whether the initial position calculation has been performed at least once.
415
+ // The reliable-anchor early-return optimisation must not fire before the first calculation,
416
+ // otherwise positionStylesOld stays as { display: 'none' } and the handle is never shown.
417
+ var hasCalculatedInitialPosition = useRef(false);
414
418
  var _useState9 = useState(Boolean(handleOptions === null || handleOptions === void 0 ? void 0 : handleOptions.isFocused)),
415
419
  _useState0 = _slicedToArray(_useState9, 2),
416
420
  isFocused = _useState0[0],
@@ -897,7 +901,17 @@ export var DragHandle = function DragHandle(_ref) {
897
901
  top: getTopPosition(dom, nodeType)
898
902
  }, height);
899
903
  }, [anchorName, getPos, view, nodeType, blockCardWidth, macroInteractionUpdates, anchorRectCache, isTopLevelNodeValue, isLayoutColumn]);
904
+ var isReliableAnchorEnabled = expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true);
905
+ var docDepForReliableAnchor = isReliableAnchorEnabled ? view.state.doc : undefined;
906
+
907
+ // Effect 1 (reliable-anchor ON): fires when non-doc deps change (e.g. blockCardWidth,
908
+ // anchorRectCache, macroInteractionUpdates, isTopLevelNodeValue, isLayoutColumn via
909
+ // calculatePositionOld) — always recalculates without the doc guard, so non-doc position
910
+ // changes are never incorrectly skipped.
900
911
  useEffect(function () {
912
+ if (!isReliableAnchorEnabled) {
913
+ return;
914
+ }
901
915
  var cleanUpTransitionListener;
902
916
  if (nodeType === 'extension' || nodeType === 'embedCard') {
903
917
  var dom = view.dom.querySelector("[".concat(getAnchorAttrName(), "=\"").concat(anchorName, "\"]"));
@@ -913,13 +927,62 @@ export var DragHandle = function DragHandle(_ref) {
913
927
  }
914
928
  var calcPos = requestAnimationFrame(function () {
915
929
  setPositionStylesOld(calculatePositionOld());
930
+ hasCalculatedInitialPosition.current = true;
916
931
  });
917
932
  return function () {
918
933
  var _cleanUpTransitionLis;
919
934
  cancelAnimationFrame(calcPos);
920
935
  (_cleanUpTransitionLis = cleanUpTransitionListener) === null || _cleanUpTransitionLis === void 0 || _cleanUpTransitionLis();
921
936
  };
922
- }, [calculatePositionOld, view.dom, anchorName, nodeType]);
937
+ }, [isReliableAnchorEnabled, calculatePositionOld, view.dom, anchorName, nodeType]);
938
+
939
+ // Effect 2 (reliable-anchor ON): fires when the doc changes — carries the DOM-attribute
940
+ // guard to skip recalc when a drag handle is active (pure keystroke during drag).
941
+ // Effect (reliable-anchor OFF): single combined effect, original behaviour.
942
+ useEffect(function () {
943
+ if (isReliableAnchorEnabled) {
944
+ // Doc-change only effect: apply the DOM-attribute guard.
945
+ // React's dep comparison already ensures this only runs when docDepForReliableAnchor
946
+ // (i.e. view.state.doc) changed, so no manual ref tracking is needed.
947
+ if (!hasCalculatedInitialPosition.current) {
948
+ return;
949
+ }
950
+ var _calcPos = requestAnimationFrame(function () {
951
+ var dom = view.dom.querySelector("[".concat(getAnchorAttrName(), "=\"").concat(anchorName, "\"]"));
952
+ if (dom !== null && dom !== void 0 && dom.hasAttribute(ACTIVE_DRAG_HANDLE_ATTR)) {
953
+ return;
954
+ }
955
+ setPositionStylesOld(calculatePositionOld());
956
+ });
957
+ return function () {
958
+ cancelAnimationFrame(_calcPos);
959
+ };
960
+ }
961
+
962
+ // reliable-anchor OFF: original single-effect behaviour (no guard).
963
+ var cleanUpTransitionListener;
964
+ if (nodeType === 'extension' || nodeType === 'embedCard') {
965
+ var dom = view.dom.querySelector("[".concat(getAnchorAttrName(), "=\"").concat(anchorName, "\"]"));
966
+ if (!dom) {
967
+ return;
968
+ }
969
+ cleanUpTransitionListener = bind(dom, {
970
+ type: 'transitionend',
971
+ listener: function listener() {
972
+ setPositionStylesOld(calculatePositionOld());
973
+ }
974
+ });
975
+ }
976
+ var calcPos = requestAnimationFrame(function () {
977
+ setPositionStylesOld(calculatePositionOld());
978
+ hasCalculatedInitialPosition.current = true;
979
+ });
980
+ return function () {
981
+ var _cleanUpTransitionLis2;
982
+ cancelAnimationFrame(calcPos);
983
+ (_cleanUpTransitionLis2 = cleanUpTransitionListener) === null || _cleanUpTransitionLis2 === void 0 || _cleanUpTransitionLis2();
984
+ };
985
+ }, [isReliableAnchorEnabled, calculatePositionOld, view.dom, anchorName, nodeType, docDepForReliableAnchor]);
923
986
  useEffect(function () {
924
987
  if (handleOptions !== null && handleOptions !== void 0 && handleOptions.isFocused && buttonRef.current) {
925
988
  var id = requestAnimationFrame(function () {
@@ -1188,9 +1251,15 @@ export var DragHandleWithVisibility = function DragHandleWithVisibility(_ref1) {
1188
1251
  // when hovering anywhere over the column, regardless of which side of the layoutSection the
1189
1252
  // column is on. (The right-side remix button is a separate node decoration and is unaffected.)
1190
1253
  var isLayoutColumn = nodeType === 'layoutColumn';
1254
+
1255
+ // Skip the right-side controlSide restriction for non-top-level nodes. The restriction exists
1256
+ // to avoid showing the drag handle and remix button simultaneously — but remix only applies to
1257
+ // top-level nodes, so non-top-level nodes should never be restricted.
1258
+ // Gated behind platform_editor_controls_reliable_anchor.
1259
+ var skipControlSideRestriction = expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && isTopLevelNode === false;
1191
1260
  return jsx(VisibilityContainer, {
1192
1261
  api: api,
1193
- controlSide: !isLayoutColumn && rightSideControlsEnabled ? 'left' : undefined,
1262
+ controlSide: !isLayoutColumn && !skipControlSideRestriction && rightSideControlsEnabled ? 'left' : undefined,
1194
1263
  forceVisibleOnMouseOut: expValEquals('platform_editor_drag_handle_keyboard_a11y', 'isEnabled', true) && !!(handleOptions !== null && handleOptions !== void 0 && handleOptions.isFocused)
1195
1264
  }, jsx(DragHandle, {
1196
1265
  view: view,
@@ -416,6 +416,10 @@ export var GlobalStylesWrapper = function GlobalStylesWrapper(_ref) {
416
416
  exposure: true
417
417
  }) ? expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? extendHoverZoneReducedNext : extendHoverZoneReduced : undefined,
418
418
  // platform_editor_controls note: this allows drag handles to render on empty lines
419
- toolbarFlagsEnabled ? undefined : withInlineNodeStyle, withDeleteLinesStyleFix, withMediaSingleStyleFix, legacyBreakoutWideLayoutStyle, headingWithIndentationInLayoutStyleFix, editorExperiment('advanced_layouts', true) ? blockCardWithoutLayout : undefined, withDividerInPanelStyleFix, withFormatInLayoutStyleFix, expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? withRelativePosStyleNext : withRelativePosStyle, topLevelNodeMarginStyles, expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? withAnchorNameZindexStyleNext : withAnchorNameZindexStyle, expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) && expValEquals('advanced_layouts', 'isEnabled', true) ? layoutColumnExtendedHoverZone : layoutColumnWithoutHoverZone, shouldRenderAnchors && (isDragging ? dragAnchorStyles : dragHandlerAnchorStyles), expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) ? staticControlsAnchorStyles : undefined]
419
+ toolbarFlagsEnabled ? undefined : withInlineNodeStyle, withDeleteLinesStyleFix, withMediaSingleStyleFix, legacyBreakoutWideLayoutStyle, headingWithIndentationInLayoutStyleFix, editorExperiment('advanced_layouts', true) ? blockCardWithoutLayout : undefined, withDividerInPanelStyleFix, withFormatInLayoutStyleFix, expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? withRelativePosStyleNext : withRelativePosStyle, topLevelNodeMarginStyles, expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? withAnchorNameZindexStyleNext : withAnchorNameZindexStyle, expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) && expValEquals('advanced_layouts', 'isEnabled', true) ? layoutColumnExtendedHoverZone : layoutColumnWithoutHoverZone, expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) ?
420
+ // dragAnchorStyles sets anchor-name on ALL nodes during drag (needed for drop target positioning).
421
+ // staticControlsAnchorStyles handles anchor-name via node decorations for non-drag state.
422
+ // shouldRenderAnchors guards both: only apply anchor styles on browsers that support CSS anchor positioning.
423
+ shouldRenderAnchors ? isDragging ? dragAnchorStyles : staticControlsAnchorStyles : false : shouldRenderAnchors && (isDragging ? dragAnchorStyles : dragHandlerAnchorStyles)]
420
424
  });
421
425
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-block-controls",
3
- "version": "13.1.12",
3
+ "version": "13.1.14",
4
4
  "description": "Block controls plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -15,9 +15,7 @@
15
15
  "module": "dist/esm/index.js",
16
16
  "module:es2019": "dist/es2019/index.js",
17
17
  "types": "dist/types/index.d.ts",
18
- "sideEffects": [
19
- "*.compiled.css"
20
- ],
18
+ "sideEffects": ["*.compiled.css"],
21
19
  "atlaskit:src": "src/index.ts",
22
20
  "dependencies": {
23
21
  "@atlaskit/browser-apis": "^1.1.0",
@@ -46,9 +44,9 @@
46
44
  "@atlaskit/pragmatic-drag-and-drop": "^2.0.0",
47
45
  "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^3.0.0",
48
46
  "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^4.1.0",
49
- "@atlaskit/primitives": "^20.3.0",
47
+ "@atlaskit/primitives": "^20.4.0",
50
48
  "@atlaskit/theme": "^26.1.0",
51
- "@atlaskit/tmp-editor-statsig": "^121.0.0",
49
+ "@atlaskit/tmp-editor-statsig": "^121.3.0",
52
50
  "@atlaskit/tokens": "^15.4.0",
53
51
  "@atlaskit/tooltip": "^23.1.0",
54
52
  "@babel/runtime": "^7.0.0",
@@ -59,47 +57,26 @@
59
57
  "uuid": "^3.1.0"
60
58
  },
61
59
  "peerDependencies": {
62
- "@atlaskit/editor-common": "^116.22.0",
60
+ "@atlaskit/editor-common": "^116.23.0",
63
61
  "react": "^18.2.0",
64
62
  "react-dom": "^18.2.0",
65
63
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
66
64
  },
67
65
  "techstack": {
68
66
  "@atlassian/frontend": {
69
- "import-structure": [
70
- "atlassian-conventions"
71
- ],
72
- "circular-dependencies": [
73
- "file-and-folder-level"
74
- ],
75
- "code-structure": [
76
- "editor-plugin"
77
- ]
67
+ "import-structure": ["atlassian-conventions"],
68
+ "circular-dependencies": ["file-and-folder-level"],
69
+ "code-structure": ["editor-plugin"]
78
70
  },
79
71
  "@repo/internal": {
80
72
  "dom-events": "use-bind-event-listener",
81
- "analytics": [
82
- "analytics-next"
83
- ],
84
- "design-tokens": [
85
- "color"
86
- ],
87
- "theming": [
88
- "react-context"
89
- ],
90
- "ui-components": [
91
- "lite-mode"
92
- ],
93
- "deprecation": [
94
- "no-deprecated-imports"
95
- ],
96
- "styling": [
97
- "emotion",
98
- "compiled"
99
- ],
100
- "imports": [
101
- "import-no-extraneous-disable-for-examples-and-docs"
102
- ]
73
+ "analytics": ["analytics-next"],
74
+ "design-tokens": ["color"],
75
+ "theming": ["react-context"],
76
+ "ui-components": ["lite-mode"],
77
+ "deprecation": ["no-deprecated-imports"],
78
+ "styling": ["emotion", "compiled"],
79
+ "imports": ["import-no-extraneous-disable-for-examples-and-docs"]
103
80
  }
104
81
  },
105
82
  "platform-feature-flags": {
@@ -130,9 +107,6 @@
130
107
  "platform_editor_table_sticky_header_patch_6": {
131
108
  "type": "boolean"
132
109
  },
133
- "platform_editor_block_menu_jira_patch_2": {
134
- "type": "boolean"
135
- },
136
110
  "confluence_remix_button_right_side_block_fg": {
137
111
  "type": "boolean"
138
112
  },