@atlaskit/editor-plugin-block-controls 12.1.1 → 12.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/pm-plugins/decorations-drag-handle.js +19 -1
  3. package/dist/cjs/pm-plugins/decorations-quick-insert-button.js +19 -1
  4. package/dist/cjs/pm-plugins/main.js +77 -19
  5. package/dist/cjs/ui/consts.js +3 -1
  6. package/dist/cjs/ui/drag-handle.js +9 -0
  7. package/dist/cjs/ui/global-styles.js +13 -1
  8. package/dist/cjs/ui/quick-insert-button.js +8 -0
  9. package/dist/es2019/pm-plugins/decorations-drag-handle.js +14 -0
  10. package/dist/es2019/pm-plugins/decorations-quick-insert-button.js +13 -0
  11. package/dist/es2019/pm-plugins/main.js +79 -21
  12. package/dist/es2019/ui/consts.js +2 -0
  13. package/dist/es2019/ui/drag-handle.js +10 -1
  14. package/dist/es2019/ui/global-styles.js +17 -2
  15. package/dist/es2019/ui/quick-insert-button.js +9 -1
  16. package/dist/esm/pm-plugins/decorations-drag-handle.js +18 -0
  17. package/dist/esm/pm-plugins/decorations-quick-insert-button.js +18 -0
  18. package/dist/esm/pm-plugins/main.js +79 -21
  19. package/dist/esm/ui/consts.js +2 -0
  20. package/dist/esm/ui/drag-handle.js +10 -1
  21. package/dist/esm/ui/global-styles.js +14 -2
  22. package/dist/esm/ui/quick-insert-button.js +9 -1
  23. package/dist/types/pm-plugins/decorations-drag-handle.d.ts +7 -0
  24. package/dist/types/pm-plugins/decorations-quick-insert-button.d.ts +7 -0
  25. package/dist/types/ui/consts.d.ts +2 -0
  26. package/dist/types-ts4.5/pm-plugins/decorations-drag-handle.d.ts +7 -0
  27. package/dist/types-ts4.5/pm-plugins/decorations-quick-insert-button.d.ts +7 -0
  28. package/dist/types-ts4.5/ui/consts.d.ts +2 -0
  29. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @atlaskit/editor-plugin-block-controls
2
2
 
3
+ ## 12.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`1f87c5cc71aa3`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/1f87c5cc71aa3) -
8
+ Improve reliability of editor controls positioning by using ProseMirror node decorations to apply
9
+ CSS anchor-name, replacing fragile CSS adjacency selectors. Gated behind
10
+ platform_editor_controls_reliable_anchor experiment.
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies
15
+
3
16
  ## 12.1.1
4
17
 
5
18
  ### Patch Changes
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.findHandleDec = exports.emptyParagraphNodeDecorations = exports.dragHandleDecoration = void 0;
7
+ exports.findHandleDec = exports.findActiveDragHandleNodeDec = exports.emptyParagraphNodeDecorations = exports.dragHandleDecoration = exports.createActiveDragHandleNodeDecoration = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
9
  var _react = require("react");
10
10
  var _bindEventListener = require("bind-event-listener");
@@ -14,11 +14,29 @@ var _view = require("@atlaskit/editor-prosemirror/view");
14
14
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
15
15
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
16
16
  var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
17
+ var _consts = require("../ui/consts");
17
18
  var _dragHandle = require("../ui/drag-handle");
18
19
  var _decorationsCommon = require("./decorations-common");
19
20
  var _marks = require("./utils/marks");
20
21
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
21
22
 
23
+ var TYPE_ACTIVE_HANDLE_DEC = 'active-drag-handle-node';
24
+
25
+ /**
26
+ * Creates a Decoration.node that marks the active node with `data-active-drag-handle="true"`.
27
+ * The CSS in staticControlsAnchorStyles then applies `anchor-name` to this attribute,
28
+ * which is more reliable than the adjacency-selector approach in dragHandlerAnchorStyles.
29
+ */
30
+ var createActiveDragHandleNodeDecoration = exports.createActiveDragHandleNodeDecoration = function createActiveDragHandleNodeDecoration(pos, nodeSize) {
31
+ return _view.Decoration.node(pos, pos + nodeSize, (0, _defineProperty2.default)({}, _consts.ACTIVE_DRAG_HANDLE_ATTR, 'true'), {
32
+ type: TYPE_ACTIVE_HANDLE_DEC
33
+ });
34
+ };
35
+ var findActiveDragHandleNodeDec = exports.findActiveDragHandleNodeDec = function findActiveDragHandleNodeDec(decorations, from, to) {
36
+ return decorations.find(from, to, function (spec) {
37
+ return spec.type === TYPE_ACTIVE_HANDLE_DEC;
38
+ });
39
+ };
22
40
  var emptyParagraphNodeDecorations = exports.emptyParagraphNodeDecorations = function emptyParagraphNodeDecorations() {
23
41
  var anchorName = "--node-anchor-paragraph-0";
24
42
  var style = "anchor-name: ".concat(anchorName, "; margin-top: 0px;");
@@ -4,23 +4,41 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.quickInsertButtonDecoration = exports.findQuickInsertInsertButtonDecoration = void 0;
7
+ exports.quickInsertButtonDecoration = exports.findQuickInsertInsertButtonDecoration = exports.findActiveQuickInsertNodeDec = exports.createActiveQuickInsertNodeDecoration = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
8
9
  var _react = require("react");
9
10
  var _uuid = _interopRequireDefault(require("uuid"));
10
11
  var _view = require("@atlaskit/editor-prosemirror/view");
11
12
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
12
13
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
13
14
  var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
15
+ var _consts = require("../ui/consts");
14
16
  var _quickInsertButton = require("../ui/quick-insert-button");
15
17
  var _marks = require("./utils/marks");
16
18
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
17
19
 
18
20
  var TYPE_QUICK_INSERT = 'INSERT_BUTTON';
21
+ var TYPE_ACTIVE_QUICK_INSERT_NODE = 'active-quick-insert-node';
19
22
  var findQuickInsertInsertButtonDecoration = exports.findQuickInsertInsertButtonDecoration = function findQuickInsertInsertButtonDecoration(decorations, from, to) {
20
23
  return decorations.find(from, to, function (spec) {
21
24
  return spec.type === TYPE_QUICK_INSERT;
22
25
  });
23
26
  };
27
+ /**
28
+ * Creates a Decoration.node that marks the active node with `data-active-quick-insert="true"`.
29
+ * The CSS in staticControlsAnchorStyles applies `anchor-name` to this attribute directly,
30
+ * replacing the unreliable adjacency selector `[block-ctrl-quick-insert-button] + *`.
31
+ */
32
+ var createActiveQuickInsertNodeDecoration = exports.createActiveQuickInsertNodeDecoration = function createActiveQuickInsertNodeDecoration(pos, nodeSize) {
33
+ return _view.Decoration.node(pos, pos + nodeSize, (0, _defineProperty2.default)({}, _consts.ACTIVE_QUICK_INSERT_ATTR, 'true'), {
34
+ type: TYPE_ACTIVE_QUICK_INSERT_NODE
35
+ });
36
+ };
37
+ var findActiveQuickInsertNodeDec = exports.findActiveQuickInsertNodeDec = function findActiveQuickInsertNodeDec(decorations, from, to) {
38
+ return decorations.find(from, to, function (spec) {
39
+ return spec.type === TYPE_ACTIVE_QUICK_INSERT_NODE;
40
+ });
41
+ };
24
42
  var quickInsertButtonDecoration = exports.quickInsertButtonDecoration = function quickInsertButtonDecoration(_ref) {
25
43
  var api = _ref.api,
26
44
  formatMessage = _ref.formatMessage,
@@ -445,21 +445,32 @@ var _apply = exports.apply = function apply(api, formatMessage, tr, currentState
445
445
  }
446
446
  }
447
447
  if (shouldRemoveHandle) {
448
- var _activeNode5, _activeNode6;
448
+ var _activeNode5, _activeNode6, _activeNode7;
449
449
  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);
450
450
  decorations = decorations.remove(oldHandle);
451
+ // When removing the handle, also remove the anchor-marker node decorations
452
+ // (data-active-drag-handle / data-active-quick-insert) so the DOM attributes
453
+ // don't linger on nodes that are no longer active.
454
+ if ((0, _expValEquals.expValEquals)('platform_editor_controls_reliable_anchor', 'isEnabled', true) && ((_activeNode7 = activeNode) === null || _activeNode7 === void 0 ? void 0 : _activeNode7.pos) !== undefined) {
455
+ var oldActiveNodeDec = (0, _decorationsDragHandle.findActiveDragHandleNodeDec)(decorations, activeNode.pos, activeNode.pos);
456
+ decorations = decorations.remove(oldActiveNodeDec);
457
+ if (activeNode.rootPos !== undefined) {
458
+ var oldActiveQuickInsertDec = (0, _decorationsQuickInsertButton.findActiveQuickInsertNodeDec)(decorations, activeNode.rootPos, activeNode.rootPos);
459
+ decorations = decorations.remove(oldActiveQuickInsertDec);
460
+ }
461
+ }
451
462
  // platform_editor_controls note: enables quick insert
452
463
  if (flags.toolbarFlagsEnabled && quickInsertButtonEnabled) {
453
- var _activeNode7, _activeNode8;
454
- 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);
464
+ var _activeNode8, _activeNode9;
465
+ 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);
455
466
  decorations = decorations.remove(oldQuickInsertButton);
456
467
  var _iterator = _createForOfIteratorHelper(nodeDecorationRegistry),
457
468
  _step;
458
469
  try {
459
470
  var _loop2 = function _loop2() {
460
- var _activeNode1, _activeNode10;
471
+ var _activeNode10, _activeNode11;
461
472
  var factory = _step.value;
462
- 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) {
473
+ 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) {
463
474
  return spec.type === factory.type;
464
475
  });
465
476
  decorations = decorations.remove(old);
@@ -479,8 +490,8 @@ var _apply = exports.apply = function apply(api, formatMessage, tr, currentState
479
490
  var _loop = function _loop() {
480
491
  var factory = _step2.value;
481
492
  if (factory.showInViewMode) {
482
- var _activeNode9, _activeNode0;
483
- 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) {
493
+ var _activeNode0, _activeNode1;
494
+ 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) {
484
495
  return spec.type === factory.type;
485
496
  });
486
497
  decorations = decorations.remove(old);
@@ -497,10 +508,10 @@ var _apply = exports.apply = function apply(api, formatMessage, tr, currentState
497
508
  }
498
509
  }
499
510
  } else if (api) {
500
- var _latestActiveNode5, _latestActiveNode1;
511
+ var _latestActiveNode5, _latestActiveNode10;
501
512
  if (shouldRecreateHandle && (!rightSideControlsEnabled || !isViewMode)) {
502
- var _activeNode11, _activeNode12, _latestActiveNode, _latestActiveNode2, _latestActiveNode3, _latestActiveNode4;
503
- 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);
513
+ var _activeNode12, _activeNode13, _latestActiveNode, _latestActiveNode2, _latestActiveNode3, _latestActiveNode4;
514
+ 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);
504
515
  decorations = decorations.remove(_oldHandle);
505
516
  var handleDec = (0, _decorationsDragHandle.dragHandleDecoration)({
506
517
  api: api,
@@ -514,12 +525,44 @@ var _apply = exports.apply = function apply(api, formatMessage, tr, currentState
514
525
  editorState: newState
515
526
  });
516
527
  decorations = decorations.add(newState.doc, [handleDec]);
528
+ if ((0, _expValEquals.expValEquals)('platform_editor_controls_reliable_anchor', 'isEnabled', true)) {
529
+ // Recreate the drag handle node decoration when the edit-mode node itself changed
530
+ // or its content was modified. Other triggers (editorSizeChanged, handleNeedsRedraw)
531
+ // don't move the decoration — DecorationSet.map() already remaps it correctly.
532
+ var needsNodeDecUpdate = activeNodeChanged || isActiveNodeModified;
533
+ if (needsNodeDecUpdate) {
534
+ var _newState$doc$nodeAt;
535
+ var nodeSize = (_newState$doc$nodeAt = newState.doc.nodeAt(latestActiveNode.pos)) === null || _newState$doc$nodeAt === void 0 ? void 0 : _newState$doc$nodeAt.nodeSize;
536
+ if (nodeSize !== undefined) {
537
+ var _activeNode14, _activeNode15;
538
+ 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);
539
+ decorations = decorations.remove(_oldActiveNodeDec);
540
+ decorations = decorations.add(newState.doc, [(0, _decorationsDragHandle.createActiveDragHandleNodeDecoration)(latestActiveNode.pos, nodeSize)]);
541
+ }
542
+ }
543
+
544
+ // The quick-insert decoration lives on the root node (rootPos), which can change
545
+ // independently of the edit-mode node — e.g. when only editorSizeChanged fires but
546
+ // rootActiveNodeChanged is also true. So we use a separate, broader guard that
547
+ // includes rootActiveNodeChanged to avoid leaving the attribute on a stale root node.
548
+ var needsQuickInsertDecUpdate = activeNodeChanged || isActiveNodeModified || rootActiveNodeChanged;
549
+ if (needsQuickInsertDecUpdate && latestActiveNode.rootPos !== undefined) {
550
+ var _newState$doc$nodeAt2;
551
+ var rootNodeSize = (_newState$doc$nodeAt2 = newState.doc.nodeAt(latestActiveNode.rootPos)) === null || _newState$doc$nodeAt2 === void 0 ? void 0 : _newState$doc$nodeAt2.nodeSize;
552
+ if (rootNodeSize !== undefined) {
553
+ var _activeNode16, _activeNode17;
554
+ 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);
555
+ decorations = decorations.remove(_oldActiveQuickInsertDec);
556
+ decorations = decorations.add(newState.doc, [(0, _decorationsQuickInsertButton.createActiveQuickInsertNodeDecoration)(latestActiveNode.rootPos, rootNodeSize)]);
557
+ }
558
+ }
559
+ }
517
560
  }
518
561
  if (shouldRecreateQuickInsertButton && ((_latestActiveNode5 = latestActiveNode) === null || _latestActiveNode5 === void 0 ? void 0 : _latestActiveNode5.rootPos) !== undefined &&
519
562
  // platform_editor_controls note: enables quick insert
520
563
  flags.toolbarFlagsEnabled && quickInsertButtonEnabled && (!rightSideControlsEnabled || !isViewMode)) {
521
- var _activeNode13, _activeNode14, _latestActiveNode6, _latestActiveNode7, _latestActiveNode8, _latestActiveNode9, _latestActiveNode0;
522
- 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);
564
+ var _activeNode18, _activeNode19, _latestActiveNode6, _latestActiveNode7, _latestActiveNode8, _latestActiveNode9, _latestActiveNode0, _latestActiveNode1;
565
+ 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);
523
566
  decorations = decorations.remove(_oldQuickInsertButton);
524
567
  var quickInsertButton = (0, _decorationsQuickInsertButton.quickInsertButtonDecoration)({
525
568
  api: api,
@@ -534,12 +577,27 @@ var _apply = exports.apply = function apply(api, formatMessage, tr, currentState
534
577
  editorState: newState
535
578
  });
536
579
  decorations = decorations.add(newState.doc, [quickInsertButton]);
580
+
581
+ // Update quick insert node decoration when the quick insert button is recreated but
582
+ // the drag handle was NOT recreated (shouldRecreateHandle was false). When
583
+ // shouldRecreateHandle is true, the block above already handles this.
584
+ if ((0, _expValEquals.expValEquals)('platform_editor_controls_reliable_anchor', 'isEnabled', true) && !shouldRecreateHandle && ((_latestActiveNode1 = latestActiveNode) === null || _latestActiveNode1 === void 0 ? void 0 : _latestActiveNode1.rootPos) !== undefined) {
585
+ var _activeNode20, _newState$doc$nodeAt3;
586
+ if (((_activeNode20 = activeNode) === null || _activeNode20 === void 0 ? void 0 : _activeNode20.rootPos) !== undefined) {
587
+ var _oldActiveQuickInsertDec2 = (0, _decorationsQuickInsertButton.findActiveQuickInsertNodeDec)(decorations, activeNode.rootPos, activeNode.rootPos);
588
+ decorations = decorations.remove(_oldActiveQuickInsertDec2);
589
+ }
590
+ var _rootNodeSize = (_newState$doc$nodeAt3 = newState.doc.nodeAt(latestActiveNode.rootPos)) === null || _newState$doc$nodeAt3 === void 0 ? void 0 : _newState$doc$nodeAt3.nodeSize;
591
+ if (_rootNodeSize !== undefined) {
592
+ decorations = decorations.add(newState.doc, [(0, _decorationsQuickInsertButton.createActiveQuickInsertNodeDecoration)(latestActiveNode.rootPos, _rootNodeSize)]);
593
+ }
594
+ }
537
595
  if (rightSideControlsEnabled) {
538
596
  var _iterator3 = _createForOfIteratorHelper(nodeDecorationRegistry),
539
597
  _step3;
540
598
  try {
541
599
  var _loop3 = function _loop3() {
542
- var _activeNode15, _activeNode16;
600
+ var _activeNode21, _activeNode22;
543
601
  var factory = _step3.value;
544
602
  if (!latestActiveNode || latestActiveNode.rootPos === undefined) {
545
603
  return 0; // continue
@@ -553,7 +611,7 @@ var _apply = exports.apply = function apply(api, formatMessage, tr, currentState
553
611
  rootAnchorName: latestActiveNode.rootAnchorName,
554
612
  rootNodeType: latestActiveNode.rootNodeType
555
613
  };
556
- 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) {
614
+ 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) {
557
615
  return spec.type === factory.type;
558
616
  });
559
617
  decorations = decorations.remove(old);
@@ -598,7 +656,7 @@ var _apply = exports.apply = function apply(api, formatMessage, tr, currentState
598
656
  }
599
657
 
600
658
  // In view mode (edit/live pages), show right-side controls on block hover (without drag handle or quick insert)
601
- var rootPos = (_latestActiveNode1 = latestActiveNode) === null || _latestActiveNode1 === void 0 ? void 0 : _latestActiveNode1.rootPos;
659
+ var rootPos = (_latestActiveNode10 = latestActiveNode) === null || _latestActiveNode10 === void 0 ? void 0 : _latestActiveNode10.rootPos;
602
660
  // rootPos is computed using the same logic as the floating insert menu, so it always points to a top-level (doc child) block when defined.
603
661
  var isDocLevel = rootPos !== undefined && !isNaN(rootPos);
604
662
  if (isViewMode && isDocLevel && flags.toolbarFlagsEnabled && rightSideControlsEnabled) {
@@ -719,15 +777,15 @@ var _apply = exports.apply = function apply(api, formatMessage, tr, currentState
719
777
  var newActiveNode;
720
778
  // platform_editor_controls note: enables quick insert
721
779
  if (flags.toolbarFlagsEnabled) {
722
- var _latestActiveNode10, _latestActiveNode11;
780
+ var _latestActiveNode11, _latestActiveNode12;
723
781
  // remove isEmptyDoc check and let decorations render and determine their own visibility
724
782
  // In view mode with right-side controls we render node decorations (right-edge button), not the
725
783
  // handle - so findHandleDec is always empty. Don't clear activeNode in that case.
726
- var hasHandleOrViewModeControls = (0, _decorationsDragHandle.findHandleDec)(decorations, (_latestActiveNode10 = latestActiveNode) === null || _latestActiveNode10 === void 0 ? void 0 : _latestActiveNode10.pos, (_latestActiveNode11 = latestActiveNode) === null || _latestActiveNode11 === void 0 ? void 0 : _latestActiveNode11.pos).length > 0 || isViewMode && rightSideControlsEnabled;
784
+ var hasHandleOrViewModeControls = (0, _decorationsDragHandle.findHandleDec)(decorations, (_latestActiveNode11 = latestActiveNode) === null || _latestActiveNode11 === void 0 ? void 0 : _latestActiveNode11.pos, (_latestActiveNode12 = latestActiveNode) === null || _latestActiveNode12 === void 0 ? void 0 : _latestActiveNode12.pos).length > 0 || isViewMode && rightSideControlsEnabled;
727
785
  newActiveNode = meta !== null && meta !== void 0 && meta.editorBlurred || !(meta !== null && meta !== void 0 && meta.activeNode) && !hasHandleOrViewModeControls ? null : latestActiveNode;
728
786
  } else {
729
- var _latestActiveNode12, _latestActiveNode13;
730
- newActiveNode = isEmptyDoc || !(meta !== null && meta !== void 0 && meta.activeNode) && (0, _decorationsDragHandle.findHandleDec)(decorations, (_latestActiveNode12 = latestActiveNode) === null || _latestActiveNode12 === void 0 ? void 0 : _latestActiveNode12.pos, (_latestActiveNode13 = latestActiveNode) === null || _latestActiveNode13 === void 0 ? void 0 : _latestActiveNode13.pos).length === 0 ? null : latestActiveNode;
787
+ var _latestActiveNode13, _latestActiveNode14;
788
+ newActiveNode = isEmptyDoc || !(meta !== null && meta !== void 0 && meta.activeNode) && (0, _decorationsDragHandle.findHandleDec)(decorations, (_latestActiveNode13 = latestActiveNode) === null || _latestActiveNode13 === void 0 ? void 0 : _latestActiveNode13.pos, (_latestActiveNode14 = latestActiveNode) === null || _latestActiveNode14 === void 0 ? void 0 : _latestActiveNode14.pos).length === 0 ? null : latestActiveNode;
731
789
  }
732
790
  var isMenuOpenNew = isMenuOpen;
733
791
  if ((0, _experiments.editorExperiment)('platform_editor_block_menu', true)) {
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.topPositionAdjustment = exports.spacingBetweenNodesForPreview = exports.spaceLookupMap = exports.rootElementGap = exports.nodeMargins = exports.getNestedNodeLeftPaddingMargin = exports.dropTargetMarginMap = exports.dragHandleGap = exports.STICKY_CONTROLS_TOP_MARGIN_FOR_STICKY_HEADER = exports.STICKY_CONTROLS_TOP_MARGIN = exports.QUICK_INSERT_WIDTH = exports.QUICK_INSERT_LEFT_OFFSET = exports.QUICK_INSERT_HEIGHT = exports.QUICK_INSERT_DIMENSIONS = exports.DRAG_HANDLE_ZINDEX = exports.DRAG_HANDLE_WRAPPED_MEDIA_EMBED_TOP_ADJUSTMENT = exports.DRAG_HANDLE_SYNCED_BLOCK_GAP = exports.DRAG_HANDLE_PARAGRAPH_TOP_ADJUSTMENT = exports.DRAG_HANDLE_PARAGRAPH_SMALL_TOP_ADJUSTMENT = exports.DRAG_HANDLE_NARROW_GAP = exports.DRAG_HANDLE_MAX_WIDTH_PLUS_GAP = exports.DRAG_HANDLE_MAX_SHIFT_CLICK_DEPTH = exports.DRAG_HANDLE_MAX_GAP = exports.DRAG_HANDLE_LAYOUT_SECTION_TOP_ADJUSTMENT = exports.DRAG_HANDLE_HEIGHT = exports.DRAG_HANDLE_H6_TOP_ADJUSTMENT = exports.DRAG_HANDLE_H5_TOP_ADJUSTMENT = exports.DRAG_HANDLE_H4_TOP_ADJUSTMENT = exports.DRAG_HANDLE_H3_TOP_ADJUSTMENT = exports.DRAG_HANDLE_H2_TOP_ADJUSTMENT = exports.DRAG_HANDLE_H1_TOP_ADJUSTMENT = exports.DRAG_HANDLE_DIVIDER_TOP_ADJUSTMENT = exports.DRAG_HANDLE_DEFAULT_GAP = exports.DRAG_HANDLE_BORDER_RADIUS = exports.DEFAULT_COLUMN_DISTRIBUTIONS = void 0;
7
+ exports.topPositionAdjustment = exports.spacingBetweenNodesForPreview = exports.spaceLookupMap = exports.rootElementGap = exports.nodeMargins = exports.getNestedNodeLeftPaddingMargin = exports.dropTargetMarginMap = exports.dragHandleGap = exports.STICKY_CONTROLS_TOP_MARGIN_FOR_STICKY_HEADER = exports.STICKY_CONTROLS_TOP_MARGIN = exports.QUICK_INSERT_WIDTH = exports.QUICK_INSERT_LEFT_OFFSET = exports.QUICK_INSERT_HEIGHT = exports.QUICK_INSERT_DIMENSIONS = exports.DRAG_HANDLE_ZINDEX = exports.DRAG_HANDLE_WRAPPED_MEDIA_EMBED_TOP_ADJUSTMENT = exports.DRAG_HANDLE_SYNCED_BLOCK_GAP = exports.DRAG_HANDLE_PARAGRAPH_TOP_ADJUSTMENT = exports.DRAG_HANDLE_PARAGRAPH_SMALL_TOP_ADJUSTMENT = exports.DRAG_HANDLE_NARROW_GAP = exports.DRAG_HANDLE_MAX_WIDTH_PLUS_GAP = exports.DRAG_HANDLE_MAX_SHIFT_CLICK_DEPTH = exports.DRAG_HANDLE_MAX_GAP = exports.DRAG_HANDLE_LAYOUT_SECTION_TOP_ADJUSTMENT = exports.DRAG_HANDLE_HEIGHT = exports.DRAG_HANDLE_H6_TOP_ADJUSTMENT = exports.DRAG_HANDLE_H5_TOP_ADJUSTMENT = exports.DRAG_HANDLE_H4_TOP_ADJUSTMENT = exports.DRAG_HANDLE_H3_TOP_ADJUSTMENT = exports.DRAG_HANDLE_H2_TOP_ADJUSTMENT = exports.DRAG_HANDLE_H1_TOP_ADJUSTMENT = exports.DRAG_HANDLE_DIVIDER_TOP_ADJUSTMENT = exports.DRAG_HANDLE_DEFAULT_GAP = exports.DRAG_HANDLE_BORDER_RADIUS = exports.DEFAULT_COLUMN_DISTRIBUTIONS = exports.ACTIVE_QUICK_INSERT_ATTR = exports.ACTIVE_DRAG_HANDLE_ATTR = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
9
  var _styles = require("@atlaskit/editor-common/styles");
10
10
  var _utils = require("@atlaskit/editor-common/utils");
@@ -12,6 +12,8 @@ var _editorSharedStyles = require("@atlaskit/editor-shared-styles");
12
12
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
13
13
  var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
14
14
  var _dropTargetMarginMap;
15
+ var ACTIVE_DRAG_HANDLE_ATTR = exports.ACTIVE_DRAG_HANDLE_ATTR = 'data-active-drag-handle';
16
+ var ACTIVE_QUICK_INSERT_ATTR = exports.ACTIVE_QUICK_INSERT_ATTR = 'data-active-quick-insert';
15
17
  var DRAG_HANDLE_HEIGHT = exports.DRAG_HANDLE_HEIGHT = 24;
16
18
  var DRAG_HANDLE_BORDER_RADIUS = exports.DRAG_HANDLE_BORDER_RADIUS = 4;
17
19
  var DRAG_HANDLE_ZINDEX = exports.DRAG_HANDLE_ZINDEX = _editorSharedStyles.akRichMediaResizeZIndex + _editorSharedStyles.akEditorUnitZIndex; //place above legacy resizer
@@ -827,6 +827,15 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
827
827
  anchorName: anchorName
828
828
  }) : anchorName;
829
829
  var dom = view.dom.querySelector("[".concat((0, _domAttrName.getAnchorAttrName)(), "=\"").concat(safeAnchorName, "\"]"));
830
+
831
+ // Defence-in-depth guard: since the node decoration sets data-active-drag-handle on the
832
+ // active node, we check for it directly. This is a cheap DOM attribute read (no reflow,
833
+ // no style recalculation) and hides the control if the decoration hasn't been applied yet.
834
+ if ((0, _expValEquals.expValEquals)('platform_editor_controls_reliable_anchor', 'isEnabled', true) && !(dom !== null && dom !== void 0 && dom.hasAttribute(_consts2.ACTIVE_DRAG_HANDLE_ATTR))) {
835
+ return {
836
+ display: 'none'
837
+ };
838
+ }
830
839
  var hasResizer = nodeType === 'table' || nodeType === 'mediaSingle';
831
840
  var isExtension = nodeType === 'extension' || nodeType === 'bodiedExtension' || nodeType === 'multiBodiedExtension' && (0, _platformFeatureFlags.fg)('confluence_frontend_native_tabs_extension');
832
841
  var isBlockCard = nodeType === 'blockCard' && !!blockCardWidth;
@@ -363,6 +363,18 @@ var dragHandlerAnchorStyles = (0, _react.css)({
363
363
  })
364
364
  }
365
365
  });
366
+ var activeControlsSelector = "[".concat(_consts.ACTIVE_DRAG_HANDLE_ATTR, "], [").concat(_consts.ACTIVE_QUICK_INSERT_ATTR, "]");
367
+
368
+ // Applies anchor-name via node decoration attributes rather than adjacency CSS selectors.
369
+ // This is more reliable than dragHandlerAnchorStyles which depends on DOM structure.
370
+ // Only nodes decorated with data-active-drag-handle / data-active-quick-insert get anchor-name.
371
+ var staticControlsAnchorStyles = (0, _react.css)({
372
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
373
+ '.ProseMirror': (0, _defineProperty2.default)({}, activeControlsSelector, {
374
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
375
+ anchorName: "var(".concat(_styles.ANCHOR_VARIABLE_NAME, ", attr(data-node-anchor type(<custom-ident>)))")
376
+ })
377
+ });
366
378
 
367
379
  // Styles applied to nodes with anchors when dragging
368
380
  var dragAnchorStyles = (0, _react.css)({
@@ -411,6 +423,6 @@ var GlobalStylesWrapper = exports.GlobalStylesWrapper = function GlobalStylesWra
411
423
  exposure: true
412
424
  }) ? (0, _expValEquals.expValEquals)('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? extendHoverZoneReducedNext : extendHoverZoneReduced : undefined,
413
425
  // platform_editor_controls note: this allows drag handles to render on empty lines
414
- 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)]
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]
415
427
  });
416
428
  };
@@ -190,6 +190,14 @@ var TypeAheadControl = exports.TypeAheadControl = function TypeAheadControl(_ref
190
190
  anchorName: rootAnchorName
191
191
  });
192
192
  var dom = view.dom.querySelector("[".concat((0, _domAttrName.getAnchorAttrName)(), "=\"").concat(safeAnchorName, "\"]"));
193
+
194
+ // Defence-in-depth guard: the node decoration sets data-active-quick-insert on the
195
+ // active root node. Check for it directly — cheap DOM attribute read, no reflow.
196
+ if ((0, _expValEquals.expValEquals)('platform_editor_controls_reliable_anchor', 'isEnabled', true) && !(dom !== null && dom !== void 0 && dom.hasAttribute(_consts.ACTIVE_QUICK_INSERT_ATTR))) {
197
+ return {
198
+ display: 'none'
199
+ };
200
+ }
193
201
  var hasResizer = rootNodeType === 'table' || rootNodeType === 'mediaSingle';
194
202
  var isExtension = rootNodeType === 'extension' || rootNodeType === 'bodiedExtension' || rootNodeType === 'multiBodiedExtension' && (0, _platformFeatureFlags.fg)('confluence_frontend_native_tabs_extension');
195
203
  var isBlockCard = rootNodeType === 'blockCard';
@@ -7,9 +7,23 @@ import { Decoration } from '@atlaskit/editor-prosemirror/view';
7
7
  import { fg } from '@atlaskit/platform-feature-flags';
8
8
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
9
9
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
10
+ import { ACTIVE_DRAG_HANDLE_ATTR } from '../ui/consts';
10
11
  import { DragHandle, DragHandleWithVisibility } from '../ui/drag-handle';
11
12
  import { TYPE_HANDLE_DEC, TYPE_NODE_DEC, unmountDecorations } from './decorations-common';
12
13
  import { getActiveBlockMarks, getMatchingBlockMarks } from './utils/marks';
14
+ const TYPE_ACTIVE_HANDLE_DEC = 'active-drag-handle-node';
15
+
16
+ /**
17
+ * Creates a Decoration.node that marks the active node with `data-active-drag-handle="true"`.
18
+ * The CSS in staticControlsAnchorStyles then applies `anchor-name` to this attribute,
19
+ * which is more reliable than the adjacency-selector approach in dragHandlerAnchorStyles.
20
+ */
21
+ export const createActiveDragHandleNodeDecoration = (pos, nodeSize) => Decoration.node(pos, pos + nodeSize, {
22
+ [ACTIVE_DRAG_HANDLE_ATTR]: 'true'
23
+ }, {
24
+ type: TYPE_ACTIVE_HANDLE_DEC
25
+ });
26
+ export const findActiveDragHandleNodeDec = (decorations, from, to) => decorations.find(from, to, spec => spec.type === TYPE_ACTIVE_HANDLE_DEC);
13
27
  export const emptyParagraphNodeDecorations = () => {
14
28
  const anchorName = `--node-anchor-paragraph-0`;
15
29
  const style = `anchor-name: ${anchorName}; margin-top: 0px;`;
@@ -5,12 +5,25 @@ import { Decoration } from '@atlaskit/editor-prosemirror/view';
5
5
  import { fg } from '@atlaskit/platform-feature-flags';
6
6
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
7
7
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
8
+ import { ACTIVE_QUICK_INSERT_ATTR } from '../ui/consts';
8
9
  import { QuickInsertWithVisibility } from '../ui/quick-insert-button';
9
10
  import { getActiveBlockMarks, getMatchingBlockMarks } from './utils/marks';
10
11
  const TYPE_QUICK_INSERT = 'INSERT_BUTTON';
12
+ const TYPE_ACTIVE_QUICK_INSERT_NODE = 'active-quick-insert-node';
11
13
  export const findQuickInsertInsertButtonDecoration = (decorations, from, to) => {
12
14
  return decorations.find(from, to, spec => spec.type === TYPE_QUICK_INSERT);
13
15
  };
16
+ /**
17
+ * Creates a Decoration.node that marks the active node with `data-active-quick-insert="true"`.
18
+ * The CSS in staticControlsAnchorStyles applies `anchor-name` to this attribute directly,
19
+ * replacing the unreliable adjacency selector `[block-ctrl-quick-insert-button] + *`.
20
+ */
21
+ export const createActiveQuickInsertNodeDecoration = (pos, nodeSize) => Decoration.node(pos, pos + nodeSize, {
22
+ [ACTIVE_QUICK_INSERT_ATTR]: 'true'
23
+ }, {
24
+ type: TYPE_ACTIVE_QUICK_INSERT_NODE
25
+ });
26
+ export const findActiveQuickInsertNodeDec = (decorations, from, to) => decorations.find(from, to, spec => spec.type === TYPE_ACTIVE_QUICK_INSERT_NODE);
14
27
  export const quickInsertButtonDecoration = ({
15
28
  api,
16
29
  formatMessage,
@@ -19,10 +19,10 @@ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
19
19
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
20
20
  import { getAnchorAttrName } from '../ui/utils/dom-attr-name';
21
21
  import { findNodeDecs, nodeDecorations } from './decorations-anchor';
22
- import { dragHandleDecoration, emptyParagraphNodeDecorations, findHandleDec } from './decorations-drag-handle';
22
+ import { createActiveDragHandleNodeDecoration, dragHandleDecoration, emptyParagraphNodeDecorations, findActiveDragHandleNodeDec, findHandleDec } from './decorations-drag-handle';
23
23
  import { dropTargetDecorations, findDropTargetDecs } from './decorations-drop-target';
24
24
  import { getActiveDropTargetDecorations } from './decorations-drop-target-active';
25
- import { findQuickInsertInsertButtonDecoration, quickInsertButtonDecoration } from './decorations-quick-insert-button';
25
+ import { createActiveQuickInsertNodeDecoration, findActiveQuickInsertNodeDec, findQuickInsertInsertButtonDecoration, quickInsertButtonDecoration } from './decorations-quick-insert-button';
26
26
  import { handleMouseDown } from './handle-mouse-down';
27
27
  import { handleMouseOver } from './handle-mouse-over';
28
28
  import { boundKeydownHandler } from './keymap';
@@ -435,34 +435,45 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
435
435
  }
436
436
  }
437
437
  if (shouldRemoveHandle) {
438
- var _activeNode5, _activeNode6;
438
+ var _activeNode5, _activeNode6, _activeNode7;
439
439
  const 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
+ // When removing the handle, also remove the anchor-marker node decorations
442
+ // (data-active-drag-handle / data-active-quick-insert) so the DOM attributes
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
+ const oldActiveNodeDec = findActiveDragHandleNodeDec(decorations, activeNode.pos, activeNode.pos);
446
+ decorations = decorations.remove(oldActiveNodeDec);
447
+ if (activeNode.rootPos !== undefined) {
448
+ const oldActiveQuickInsertDec = findActiveQuickInsertNodeDec(decorations, activeNode.rootPos, activeNode.rootPos);
449
+ decorations = decorations.remove(oldActiveQuickInsertDec);
450
+ }
451
+ }
441
452
  // platform_editor_controls note: enables quick insert
442
453
  if (flags.toolbarFlagsEnabled && quickInsertButtonEnabled) {
443
- var _activeNode7, _activeNode8;
444
- const oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode7 = activeNode) === null || _activeNode7 === void 0 ? void 0 : _activeNode7.rootPos, (_activeNode8 = activeNode) === null || _activeNode8 === void 0 ? void 0 : _activeNode8.rootPos);
454
+ var _activeNode8, _activeNode9;
455
+ const oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode8 = activeNode) === null || _activeNode8 === void 0 ? void 0 : _activeNode8.rootPos, (_activeNode9 = activeNode) === null || _activeNode9 === void 0 ? void 0 : _activeNode9.rootPos);
445
456
  decorations = decorations.remove(oldQuickInsertButton);
446
457
  for (const factory of nodeDecorationRegistry) {
447
- var _activeNode9, _activeNode0;
448
- 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);
458
+ var _activeNode0, _activeNode1;
459
+ 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);
449
460
  decorations = decorations.remove(old);
450
461
  }
451
462
  if (rightSideControlsEnabled && isViewMode && fg('confluence_remix_button_right_side_block_fg')) {
452
463
  for (const factory of nodeDecorationRegistry) {
453
464
  if (factory.showInViewMode) {
454
- var _activeNode1, _activeNode10;
455
- 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);
465
+ var _activeNode10, _activeNode11;
466
+ 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);
456
467
  decorations = decorations.remove(old);
457
468
  }
458
469
  }
459
470
  }
460
471
  }
461
472
  } else if (api) {
462
- var _latestActiveNode5, _latestActiveNode1;
473
+ var _latestActiveNode5, _latestActiveNode10;
463
474
  if (shouldRecreateHandle && (!rightSideControlsEnabled || !isViewMode)) {
464
- var _activeNode11, _activeNode12, _latestActiveNode, _latestActiveNode2, _latestActiveNode3, _latestActiveNode4;
465
- const oldHandle = findHandleDec(decorations, (_activeNode11 = activeNode) === null || _activeNode11 === void 0 ? void 0 : _activeNode11.pos, (_activeNode12 = activeNode) === null || _activeNode12 === void 0 ? void 0 : _activeNode12.pos);
475
+ var _activeNode12, _activeNode13, _latestActiveNode, _latestActiveNode2, _latestActiveNode3, _latestActiveNode4;
476
+ const oldHandle = findHandleDec(decorations, (_activeNode12 = activeNode) === null || _activeNode12 === void 0 ? void 0 : _activeNode12.pos, (_activeNode13 = activeNode) === null || _activeNode13 === void 0 ? void 0 : _activeNode13.pos);
466
477
  decorations = decorations.remove(oldHandle);
467
478
  const handleDec = dragHandleDecoration({
468
479
  api,
@@ -476,12 +487,44 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
476
487
  editorState: newState
477
488
  });
478
489
  decorations = decorations.add(newState.doc, [handleDec]);
490
+ if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true)) {
491
+ // Recreate the drag handle node decoration when the edit-mode node itself changed
492
+ // or its content was modified. Other triggers (editorSizeChanged, handleNeedsRedraw)
493
+ // don't move the decoration — DecorationSet.map() already remaps it correctly.
494
+ const needsNodeDecUpdate = activeNodeChanged || isActiveNodeModified;
495
+ if (needsNodeDecUpdate) {
496
+ var _newState$doc$nodeAt;
497
+ const nodeSize = (_newState$doc$nodeAt = newState.doc.nodeAt(latestActiveNode.pos)) === null || _newState$doc$nodeAt === void 0 ? void 0 : _newState$doc$nodeAt.nodeSize;
498
+ if (nodeSize !== undefined) {
499
+ var _activeNode14, _activeNode15;
500
+ const oldActiveNodeDec = findActiveDragHandleNodeDec(decorations, (_activeNode14 = activeNode) === null || _activeNode14 === void 0 ? void 0 : _activeNode14.pos, (_activeNode15 = activeNode) === null || _activeNode15 === void 0 ? void 0 : _activeNode15.pos);
501
+ decorations = decorations.remove(oldActiveNodeDec);
502
+ decorations = decorations.add(newState.doc, [createActiveDragHandleNodeDecoration(latestActiveNode.pos, nodeSize)]);
503
+ }
504
+ }
505
+
506
+ // The quick-insert decoration lives on the root node (rootPos), which can change
507
+ // independently of the edit-mode node — e.g. when only editorSizeChanged fires but
508
+ // rootActiveNodeChanged is also true. So we use a separate, broader guard that
509
+ // includes rootActiveNodeChanged to avoid leaving the attribute on a stale root node.
510
+ const needsQuickInsertDecUpdate = activeNodeChanged || isActiveNodeModified || rootActiveNodeChanged;
511
+ if (needsQuickInsertDecUpdate && latestActiveNode.rootPos !== undefined) {
512
+ var _newState$doc$nodeAt2;
513
+ const rootNodeSize = (_newState$doc$nodeAt2 = newState.doc.nodeAt(latestActiveNode.rootPos)) === null || _newState$doc$nodeAt2 === void 0 ? void 0 : _newState$doc$nodeAt2.nodeSize;
514
+ if (rootNodeSize !== undefined) {
515
+ var _activeNode16, _activeNode17;
516
+ const oldActiveQuickInsertDec = findActiveQuickInsertNodeDec(decorations, (_activeNode16 = activeNode) === null || _activeNode16 === void 0 ? void 0 : _activeNode16.rootPos, (_activeNode17 = activeNode) === null || _activeNode17 === void 0 ? void 0 : _activeNode17.rootPos);
517
+ decorations = decorations.remove(oldActiveQuickInsertDec);
518
+ decorations = decorations.add(newState.doc, [createActiveQuickInsertNodeDecoration(latestActiveNode.rootPos, rootNodeSize)]);
519
+ }
520
+ }
521
+ }
479
522
  }
480
523
  if (shouldRecreateQuickInsertButton && ((_latestActiveNode5 = latestActiveNode) === null || _latestActiveNode5 === void 0 ? void 0 : _latestActiveNode5.rootPos) !== undefined &&
481
524
  // platform_editor_controls note: enables quick insert
482
525
  flags.toolbarFlagsEnabled && quickInsertButtonEnabled && (!rightSideControlsEnabled || !isViewMode)) {
483
- var _activeNode13, _activeNode14, _latestActiveNode6, _latestActiveNode7, _latestActiveNode8, _latestActiveNode9, _latestActiveNode0;
484
- const oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode13 = activeNode) === null || _activeNode13 === void 0 ? void 0 : _activeNode13.rootPos, (_activeNode14 = activeNode) === null || _activeNode14 === void 0 ? void 0 : _activeNode14.rootPos);
526
+ var _activeNode18, _activeNode19, _latestActiveNode6, _latestActiveNode7, _latestActiveNode8, _latestActiveNode9, _latestActiveNode0, _latestActiveNode1;
527
+ const oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode18 = activeNode) === null || _activeNode18 === void 0 ? void 0 : _activeNode18.rootPos, (_activeNode19 = activeNode) === null || _activeNode19 === void 0 ? void 0 : _activeNode19.rootPos);
485
528
  decorations = decorations.remove(oldQuickInsertButton);
486
529
  const quickInsertButton = quickInsertButtonDecoration({
487
530
  api,
@@ -496,9 +539,24 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
496
539
  editorState: newState
497
540
  });
498
541
  decorations = decorations.add(newState.doc, [quickInsertButton]);
542
+
543
+ // Update quick insert node decoration when the quick insert button is recreated but
544
+ // the drag handle was NOT recreated (shouldRecreateHandle was false). When
545
+ // shouldRecreateHandle is true, the block above already handles this.
546
+ if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && !shouldRecreateHandle && ((_latestActiveNode1 = latestActiveNode) === null || _latestActiveNode1 === void 0 ? void 0 : _latestActiveNode1.rootPos) !== undefined) {
547
+ var _activeNode20, _newState$doc$nodeAt3;
548
+ if (((_activeNode20 = activeNode) === null || _activeNode20 === void 0 ? void 0 : _activeNode20.rootPos) !== undefined) {
549
+ const oldActiveQuickInsertDec = findActiveQuickInsertNodeDec(decorations, activeNode.rootPos, activeNode.rootPos);
550
+ decorations = decorations.remove(oldActiveQuickInsertDec);
551
+ }
552
+ const rootNodeSize = (_newState$doc$nodeAt3 = newState.doc.nodeAt(latestActiveNode.rootPos)) === null || _newState$doc$nodeAt3 === void 0 ? void 0 : _newState$doc$nodeAt3.nodeSize;
553
+ if (rootNodeSize !== undefined) {
554
+ decorations = decorations.add(newState.doc, [createActiveQuickInsertNodeDecoration(latestActiveNode.rootPos, rootNodeSize)]);
555
+ }
556
+ }
499
557
  if (rightSideControlsEnabled) {
500
558
  for (const factory of nodeDecorationRegistry) {
501
- var _activeNode15, _activeNode16;
559
+ var _activeNode21, _activeNode22;
502
560
  if (!latestActiveNode || latestActiveNode.rootPos === undefined) {
503
561
  continue;
504
562
  }
@@ -511,7 +569,7 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
511
569
  rootAnchorName: latestActiveNode.rootAnchorName,
512
570
  rootNodeType: latestActiveNode.rootNodeType
513
571
  };
514
- 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);
572
+ 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);
515
573
  decorations = decorations.remove(old);
516
574
 
517
575
  // determines whether to show the decorations, see malleableUiPlugin.tsx
@@ -530,7 +588,7 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
530
588
  }
531
589
 
532
590
  // In view mode (edit/live pages), show right-side controls on block hover (without drag handle or quick insert)
533
- const rootPos = (_latestActiveNode1 = latestActiveNode) === null || _latestActiveNode1 === void 0 ? void 0 : _latestActiveNode1.rootPos;
591
+ const rootPos = (_latestActiveNode10 = latestActiveNode) === null || _latestActiveNode10 === void 0 ? void 0 : _latestActiveNode10.rootPos;
534
592
  // rootPos is computed using the same logic as the floating insert menu, so it always points to a top-level (doc child) block when defined.
535
593
  const isDocLevel = rootPos !== undefined && !isNaN(rootPos);
536
594
  if (isViewMode && isDocLevel && flags.toolbarFlagsEnabled && rightSideControlsEnabled) {
@@ -620,15 +678,15 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
620
678
  let newActiveNode;
621
679
  // platform_editor_controls note: enables quick insert
622
680
  if (flags.toolbarFlagsEnabled) {
623
- var _latestActiveNode10, _latestActiveNode11;
681
+ var _latestActiveNode11, _latestActiveNode12;
624
682
  // remove isEmptyDoc check and let decorations render and determine their own visibility
625
683
  // In view mode with right-side controls we render node decorations (right-edge button), not the
626
684
  // handle - so findHandleDec is always empty. Don't clear activeNode in that case.
627
- const hasHandleOrViewModeControls = findHandleDec(decorations, (_latestActiveNode10 = latestActiveNode) === null || _latestActiveNode10 === void 0 ? void 0 : _latestActiveNode10.pos, (_latestActiveNode11 = latestActiveNode) === null || _latestActiveNode11 === void 0 ? void 0 : _latestActiveNode11.pos).length > 0 || isViewMode && rightSideControlsEnabled;
685
+ const hasHandleOrViewModeControls = findHandleDec(decorations, (_latestActiveNode11 = latestActiveNode) === null || _latestActiveNode11 === void 0 ? void 0 : _latestActiveNode11.pos, (_latestActiveNode12 = latestActiveNode) === null || _latestActiveNode12 === void 0 ? void 0 : _latestActiveNode12.pos).length > 0 || isViewMode && rightSideControlsEnabled;
628
686
  newActiveNode = meta !== null && meta !== void 0 && meta.editorBlurred || !(meta !== null && meta !== void 0 && meta.activeNode) && !hasHandleOrViewModeControls ? null : latestActiveNode;
629
687
  } else {
630
- var _latestActiveNode12, _latestActiveNode13;
631
- newActiveNode = isEmptyDoc || !(meta !== null && meta !== void 0 && meta.activeNode) && findHandleDec(decorations, (_latestActiveNode12 = latestActiveNode) === null || _latestActiveNode12 === void 0 ? void 0 : _latestActiveNode12.pos, (_latestActiveNode13 = latestActiveNode) === null || _latestActiveNode13 === void 0 ? void 0 : _latestActiveNode13.pos).length === 0 ? null : latestActiveNode;
688
+ var _latestActiveNode13, _latestActiveNode14;
689
+ newActiveNode = isEmptyDoc || !(meta !== null && meta !== void 0 && meta.activeNode) && findHandleDec(decorations, (_latestActiveNode13 = latestActiveNode) === null || _latestActiveNode13 === void 0 ? void 0 : _latestActiveNode13.pos, (_latestActiveNode14 = latestActiveNode) === null || _latestActiveNode14 === void 0 ? void 0 : _latestActiveNode14.pos).length === 0 ? null : latestActiveNode;
632
690
  }
633
691
  let isMenuOpenNew = isMenuOpen;
634
692
  if (editorExperiment('platform_editor_block_menu', true)) {
@@ -3,6 +3,8 @@ import { breakoutResizableNodes as breakoutResizableNodesNew } from '@atlaskit/e
3
3
  import { akEditorUnitZIndex, akRichMediaResizeZIndex } from '@atlaskit/editor-shared-styles';
4
4
  import { fg } from '@atlaskit/platform-feature-flags';
5
5
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
6
+ export const ACTIVE_DRAG_HANDLE_ATTR = 'data-active-drag-handle';
7
+ export const ACTIVE_QUICK_INSERT_ATTR = 'data-active-quick-insert';
6
8
  export const DRAG_HANDLE_HEIGHT = 24;
7
9
  export const DRAG_HANDLE_BORDER_RADIUS = 4;
8
10
  export const DRAG_HANDLE_ZINDEX = akRichMediaResizeZIndex + akEditorUnitZIndex; //place above legacy resizer
@@ -35,7 +35,7 @@ import { getControlBottomCSSValue, getControlHeightCSSValue, getLeftPosition, ge
35
35
  import { expandAndUpdateSelection } from '../pm-plugins/utils/expand-and-update-selection';
36
36
  import { isHandleCorrelatedToSelection, selectNode } from '../pm-plugins/utils/getSelection';
37
37
  import { alignAnchorHeadInDirectionOfPos, expandSelectionHeadToNodeAtPos } from '../pm-plugins/utils/selection';
38
- import { DRAG_HANDLE_BORDER_RADIUS, DRAG_HANDLE_HEIGHT, DRAG_HANDLE_MAX_SHIFT_CLICK_DEPTH, DRAG_HANDLE_ZINDEX, dragHandleGap, nodeMargins, spacingBetweenNodesForPreview, STICKY_CONTROLS_TOP_MARGIN, STICKY_CONTROLS_TOP_MARGIN_FOR_STICKY_HEADER, topPositionAdjustment } from './consts';
38
+ import { ACTIVE_DRAG_HANDLE_ATTR, DRAG_HANDLE_BORDER_RADIUS, DRAG_HANDLE_HEIGHT, DRAG_HANDLE_MAX_SHIFT_CLICK_DEPTH, DRAG_HANDLE_ZINDEX, dragHandleGap, nodeMargins, spacingBetweenNodesForPreview, STICKY_CONTROLS_TOP_MARGIN, STICKY_CONTROLS_TOP_MARGIN_FOR_STICKY_HEADER, topPositionAdjustment } from './consts';
39
39
  import { DragHandleNestedIcon } from './drag-handle-nested-icon';
40
40
  import { dragPreview } from './drag-preview';
41
41
  import { refreshAnchorName } from './utils/anchor-name';
@@ -807,6 +807,15 @@ export const DragHandle = ({
807
807
  anchorName
808
808
  }) : anchorName;
809
809
  const dom = view.dom.querySelector(`[${getAnchorAttrName()}="${safeAnchorName}"]`);
810
+
811
+ // Defence-in-depth guard: since the node decoration sets data-active-drag-handle on the
812
+ // active node, we check for it directly. This is a cheap DOM attribute read (no reflow,
813
+ // no style recalculation) and hides the control if the decoration hasn't been applied yet.
814
+ if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && !(dom !== null && dom !== void 0 && dom.hasAttribute(ACTIVE_DRAG_HANDLE_ATTR))) {
815
+ return {
816
+ display: 'none'
817
+ };
818
+ }
810
819
  const hasResizer = nodeType === 'table' || nodeType === 'mediaSingle';
811
820
  const isExtension = nodeType === 'extension' || nodeType === 'bodiedExtension' || nodeType === 'multiBodiedExtension' && fg('confluence_frontend_native_tabs_extension');
812
821
  const isBlockCard = nodeType === 'blockCard' && !!blockCardWidth;
@@ -11,7 +11,7 @@ import { ZERO_WIDTH_SPACE } from '@atlaskit/editor-common/whitespace';
11
11
  import { akEditorBreakoutPadding, akEditorCalculatedWideLayoutWidth, akEditorCalculatedWideLayoutWidthSmallViewport, akEditorFullPageNarrowBreakout, akEditorGutterPaddingDynamic, akEditorGutterPaddingReduced } from '@atlaskit/editor-shared-styles';
12
12
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
13
13
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
14
- import { DRAG_HANDLE_MAX_WIDTH_PLUS_GAP } from './consts';
14
+ import { ACTIVE_DRAG_HANDLE_ATTR, ACTIVE_QUICK_INSERT_ATTR, DRAG_HANDLE_MAX_WIDTH_PLUS_GAP } from './consts';
15
15
  import { NODE_ANCHOR_ATTR_NAME } from './utils/dom-attr-name';
16
16
 
17
17
  /**
@@ -449,6 +449,21 @@ const dragHandlerAnchorStyles = css({
449
449
  }
450
450
  }
451
451
  });
452
+ const activeControlsSelector = `[${ACTIVE_DRAG_HANDLE_ATTR}], [${ACTIVE_QUICK_INSERT_ATTR}]`;
453
+
454
+ // Applies anchor-name via node decoration attributes rather than adjacency CSS selectors.
455
+ // This is more reliable than dragHandlerAnchorStyles which depends on DOM structure.
456
+ // Only nodes decorated with data-active-drag-handle / data-active-quick-insert get anchor-name.
457
+ const staticControlsAnchorStyles = css({
458
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
459
+ '.ProseMirror': {
460
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors, @atlaskit/ui-styling-standard/no-unsafe-values
461
+ [activeControlsSelector]: {
462
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
463
+ anchorName: `var(${ANCHOR_VARIABLE_NAME}, attr(data-node-anchor type(<custom-ident>)))`
464
+ }
465
+ }
466
+ });
452
467
 
453
468
  // Styles applied to nodes with anchors when dragging
454
469
  const dragAnchorStyles = css({
@@ -499,6 +514,6 @@ export const GlobalStylesWrapper = ({
499
514
  exposure: true
500
515
  }) ? expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? extendHoverZoneReducedNext : extendHoverZoneReduced : undefined,
501
516
  // platform_editor_controls note: this allows drag handles to render on empty lines
502
- 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)]
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]
503
518
  });
504
519
  };
@@ -24,7 +24,7 @@ import Tooltip from '@atlaskit/tooltip';
24
24
  import { getNodeTypeWithLevel } from '../pm-plugins/decorations-common';
25
25
  import { getControlBottomCSSValue, getControlHeightCSSValue, getNodeHeight, getTopPosition, shouldBeSticky } from '../pm-plugins/utils/drag-handle-positions';
26
26
  import { getLeftPositionForRootElement } from '../pm-plugins/utils/widget-positions';
27
- import { QUICK_INSERT_DIMENSIONS, QUICK_INSERT_HEIGHT, QUICK_INSERT_LEFT_OFFSET, QUICK_INSERT_WIDTH, rootElementGap, STICKY_CONTROLS_TOP_MARGIN_FOR_STICKY_HEADER, topPositionAdjustment } from './consts';
27
+ import { ACTIVE_QUICK_INSERT_ATTR, QUICK_INSERT_DIMENSIONS, QUICK_INSERT_HEIGHT, QUICK_INSERT_LEFT_OFFSET, QUICK_INSERT_WIDTH, rootElementGap, STICKY_CONTROLS_TOP_MARGIN_FOR_STICKY_HEADER, topPositionAdjustment } from './consts';
28
28
  import { refreshAnchorName } from './utils/anchor-name';
29
29
  import { isInTextSelection, isNestedNodeSelected, isNonEditableBlock, isSelectionInNode } from './utils/document-checks';
30
30
  import { getAnchorAttrName } from './utils/dom-attr-name';
@@ -179,6 +179,14 @@ export const TypeAheadControl = ({
179
179
  anchorName: rootAnchorName
180
180
  });
181
181
  const dom = view.dom.querySelector(`[${getAnchorAttrName()}="${safeAnchorName}"]`);
182
+
183
+ // Defence-in-depth guard: the node decoration sets data-active-quick-insert on the
184
+ // active root node. Check for it directly — cheap DOM attribute read, no reflow.
185
+ if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && !(dom !== null && dom !== void 0 && dom.hasAttribute(ACTIVE_QUICK_INSERT_ATTR))) {
186
+ return {
187
+ display: 'none'
188
+ };
189
+ }
182
190
  const hasResizer = rootNodeType === 'table' || rootNodeType === 'mediaSingle';
183
191
  const isExtension = rootNodeType === 'extension' || rootNodeType === 'bodiedExtension' || rootNodeType === 'multiBodiedExtension' && fg('confluence_frontend_native_tabs_extension');
184
192
  const isBlockCard = rootNodeType === 'blockCard';
@@ -8,9 +8,27 @@ import { Decoration } from '@atlaskit/editor-prosemirror/view';
8
8
  import { fg } from '@atlaskit/platform-feature-flags';
9
9
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
10
10
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
11
+ import { ACTIVE_DRAG_HANDLE_ATTR } from '../ui/consts';
11
12
  import { DragHandle, DragHandleWithVisibility } from '../ui/drag-handle';
12
13
  import { TYPE_HANDLE_DEC, TYPE_NODE_DEC, unmountDecorations } from './decorations-common';
13
14
  import { getActiveBlockMarks, getMatchingBlockMarks } from './utils/marks';
15
+ var TYPE_ACTIVE_HANDLE_DEC = 'active-drag-handle-node';
16
+
17
+ /**
18
+ * Creates a Decoration.node that marks the active node with `data-active-drag-handle="true"`.
19
+ * The CSS in staticControlsAnchorStyles then applies `anchor-name` to this attribute,
20
+ * which is more reliable than the adjacency-selector approach in dragHandlerAnchorStyles.
21
+ */
22
+ export var createActiveDragHandleNodeDecoration = function createActiveDragHandleNodeDecoration(pos, nodeSize) {
23
+ return Decoration.node(pos, pos + nodeSize, _defineProperty({}, ACTIVE_DRAG_HANDLE_ATTR, 'true'), {
24
+ type: TYPE_ACTIVE_HANDLE_DEC
25
+ });
26
+ };
27
+ export var findActiveDragHandleNodeDec = function findActiveDragHandleNodeDec(decorations, from, to) {
28
+ return decorations.find(from, to, function (spec) {
29
+ return spec.type === TYPE_ACTIVE_HANDLE_DEC;
30
+ });
31
+ };
14
32
  export var emptyParagraphNodeDecorations = function emptyParagraphNodeDecorations() {
15
33
  var anchorName = "--node-anchor-paragraph-0";
16
34
  var style = "anchor-name: ".concat(anchorName, "; margin-top: 0px;");
@@ -1,3 +1,4 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
1
2
  import { createElement } from 'react';
2
3
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
3
4
  import uuid from 'uuid';
@@ -5,14 +6,31 @@ import { Decoration } from '@atlaskit/editor-prosemirror/view';
5
6
  import { fg } from '@atlaskit/platform-feature-flags';
6
7
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
7
8
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
9
+ import { ACTIVE_QUICK_INSERT_ATTR } from '../ui/consts';
8
10
  import { QuickInsertWithVisibility } from '../ui/quick-insert-button';
9
11
  import { getActiveBlockMarks, getMatchingBlockMarks } from './utils/marks';
10
12
  var TYPE_QUICK_INSERT = 'INSERT_BUTTON';
13
+ var TYPE_ACTIVE_QUICK_INSERT_NODE = 'active-quick-insert-node';
11
14
  export var findQuickInsertInsertButtonDecoration = function findQuickInsertInsertButtonDecoration(decorations, from, to) {
12
15
  return decorations.find(from, to, function (spec) {
13
16
  return spec.type === TYPE_QUICK_INSERT;
14
17
  });
15
18
  };
19
+ /**
20
+ * Creates a Decoration.node that marks the active node with `data-active-quick-insert="true"`.
21
+ * The CSS in staticControlsAnchorStyles applies `anchor-name` to this attribute directly,
22
+ * replacing the unreliable adjacency selector `[block-ctrl-quick-insert-button] + *`.
23
+ */
24
+ export var createActiveQuickInsertNodeDecoration = function createActiveQuickInsertNodeDecoration(pos, nodeSize) {
25
+ return Decoration.node(pos, pos + nodeSize, _defineProperty({}, ACTIVE_QUICK_INSERT_ATTR, 'true'), {
26
+ type: TYPE_ACTIVE_QUICK_INSERT_NODE
27
+ });
28
+ };
29
+ export var findActiveQuickInsertNodeDec = function findActiveQuickInsertNodeDec(decorations, from, to) {
30
+ return decorations.find(from, to, function (spec) {
31
+ return spec.type === TYPE_ACTIVE_QUICK_INSERT_NODE;
32
+ });
33
+ };
16
34
  export var quickInsertButtonDecoration = function quickInsertButtonDecoration(_ref) {
17
35
  var api = _ref.api,
18
36
  formatMessage = _ref.formatMessage,
@@ -25,10 +25,10 @@ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
25
25
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
26
26
  import { getAnchorAttrName } from '../ui/utils/dom-attr-name';
27
27
  import { findNodeDecs, nodeDecorations } from './decorations-anchor';
28
- import { dragHandleDecoration, emptyParagraphNodeDecorations, findHandleDec } from './decorations-drag-handle';
28
+ import { createActiveDragHandleNodeDecoration, dragHandleDecoration, emptyParagraphNodeDecorations, findActiveDragHandleNodeDec, findHandleDec } from './decorations-drag-handle';
29
29
  import { dropTargetDecorations, findDropTargetDecs } from './decorations-drop-target';
30
30
  import { getActiveDropTargetDecorations } from './decorations-drop-target-active';
31
- import { findQuickInsertInsertButtonDecoration, quickInsertButtonDecoration } from './decorations-quick-insert-button';
31
+ import { createActiveQuickInsertNodeDecoration, findActiveQuickInsertNodeDec, findQuickInsertInsertButtonDecoration, quickInsertButtonDecoration } from './decorations-quick-insert-button';
32
32
  import { handleMouseDown } from './handle-mouse-down';
33
33
  import { handleMouseOver } from './handle-mouse-over';
34
34
  import { boundKeydownHandler } from './keymap';
@@ -438,21 +438,32 @@ var _apply = function apply(api, formatMessage, tr, currentState, newState, flag
438
438
  }
439
439
  }
440
440
  if (shouldRemoveHandle) {
441
- var _activeNode5, _activeNode6;
441
+ var _activeNode5, _activeNode6, _activeNode7;
442
442
  var oldHandle = findHandleDec(decorations, (_activeNode5 = activeNode) === null || _activeNode5 === void 0 ? void 0 : _activeNode5.pos, (_activeNode6 = activeNode) === null || _activeNode6 === void 0 ? void 0 : _activeNode6.pos);
443
443
  decorations = decorations.remove(oldHandle);
444
+ // When removing the handle, also remove the anchor-marker node decorations
445
+ // (data-active-drag-handle / data-active-quick-insert) so the DOM attributes
446
+ // don't linger on nodes that are no longer active.
447
+ if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && ((_activeNode7 = activeNode) === null || _activeNode7 === void 0 ? void 0 : _activeNode7.pos) !== undefined) {
448
+ var oldActiveNodeDec = findActiveDragHandleNodeDec(decorations, activeNode.pos, activeNode.pos);
449
+ decorations = decorations.remove(oldActiveNodeDec);
450
+ if (activeNode.rootPos !== undefined) {
451
+ var oldActiveQuickInsertDec = findActiveQuickInsertNodeDec(decorations, activeNode.rootPos, activeNode.rootPos);
452
+ decorations = decorations.remove(oldActiveQuickInsertDec);
453
+ }
454
+ }
444
455
  // platform_editor_controls note: enables quick insert
445
456
  if (flags.toolbarFlagsEnabled && quickInsertButtonEnabled) {
446
- var _activeNode7, _activeNode8;
447
- var oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode7 = activeNode) === null || _activeNode7 === void 0 ? void 0 : _activeNode7.rootPos, (_activeNode8 = activeNode) === null || _activeNode8 === void 0 ? void 0 : _activeNode8.rootPos);
457
+ var _activeNode8, _activeNode9;
458
+ var oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode8 = activeNode) === null || _activeNode8 === void 0 ? void 0 : _activeNode8.rootPos, (_activeNode9 = activeNode) === null || _activeNode9 === void 0 ? void 0 : _activeNode9.rootPos);
448
459
  decorations = decorations.remove(oldQuickInsertButton);
449
460
  var _iterator = _createForOfIteratorHelper(nodeDecorationRegistry),
450
461
  _step;
451
462
  try {
452
463
  var _loop2 = function _loop2() {
453
- var _activeNode1, _activeNode10;
464
+ var _activeNode10, _activeNode11;
454
465
  var factory = _step.value;
455
- 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) {
466
+ 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) {
456
467
  return spec.type === factory.type;
457
468
  });
458
469
  decorations = decorations.remove(old);
@@ -472,8 +483,8 @@ var _apply = function apply(api, formatMessage, tr, currentState, newState, flag
472
483
  var _loop = function _loop() {
473
484
  var factory = _step2.value;
474
485
  if (factory.showInViewMode) {
475
- var _activeNode9, _activeNode0;
476
- 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) {
486
+ var _activeNode0, _activeNode1;
487
+ 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) {
477
488
  return spec.type === factory.type;
478
489
  });
479
490
  decorations = decorations.remove(old);
@@ -490,10 +501,10 @@ var _apply = function apply(api, formatMessage, tr, currentState, newState, flag
490
501
  }
491
502
  }
492
503
  } else if (api) {
493
- var _latestActiveNode5, _latestActiveNode1;
504
+ var _latestActiveNode5, _latestActiveNode10;
494
505
  if (shouldRecreateHandle && (!rightSideControlsEnabled || !isViewMode)) {
495
- var _activeNode11, _activeNode12, _latestActiveNode, _latestActiveNode2, _latestActiveNode3, _latestActiveNode4;
496
- var _oldHandle = findHandleDec(decorations, (_activeNode11 = activeNode) === null || _activeNode11 === void 0 ? void 0 : _activeNode11.pos, (_activeNode12 = activeNode) === null || _activeNode12 === void 0 ? void 0 : _activeNode12.pos);
506
+ var _activeNode12, _activeNode13, _latestActiveNode, _latestActiveNode2, _latestActiveNode3, _latestActiveNode4;
507
+ var _oldHandle = findHandleDec(decorations, (_activeNode12 = activeNode) === null || _activeNode12 === void 0 ? void 0 : _activeNode12.pos, (_activeNode13 = activeNode) === null || _activeNode13 === void 0 ? void 0 : _activeNode13.pos);
497
508
  decorations = decorations.remove(_oldHandle);
498
509
  var handleDec = dragHandleDecoration({
499
510
  api: api,
@@ -507,12 +518,44 @@ var _apply = function apply(api, formatMessage, tr, currentState, newState, flag
507
518
  editorState: newState
508
519
  });
509
520
  decorations = decorations.add(newState.doc, [handleDec]);
521
+ if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true)) {
522
+ // Recreate the drag handle node decoration when the edit-mode node itself changed
523
+ // or its content was modified. Other triggers (editorSizeChanged, handleNeedsRedraw)
524
+ // don't move the decoration — DecorationSet.map() already remaps it correctly.
525
+ var needsNodeDecUpdate = activeNodeChanged || isActiveNodeModified;
526
+ if (needsNodeDecUpdate) {
527
+ var _newState$doc$nodeAt;
528
+ var nodeSize = (_newState$doc$nodeAt = newState.doc.nodeAt(latestActiveNode.pos)) === null || _newState$doc$nodeAt === void 0 ? void 0 : _newState$doc$nodeAt.nodeSize;
529
+ if (nodeSize !== undefined) {
530
+ var _activeNode14, _activeNode15;
531
+ var _oldActiveNodeDec = findActiveDragHandleNodeDec(decorations, (_activeNode14 = activeNode) === null || _activeNode14 === void 0 ? void 0 : _activeNode14.pos, (_activeNode15 = activeNode) === null || _activeNode15 === void 0 ? void 0 : _activeNode15.pos);
532
+ decorations = decorations.remove(_oldActiveNodeDec);
533
+ decorations = decorations.add(newState.doc, [createActiveDragHandleNodeDecoration(latestActiveNode.pos, nodeSize)]);
534
+ }
535
+ }
536
+
537
+ // The quick-insert decoration lives on the root node (rootPos), which can change
538
+ // independently of the edit-mode node — e.g. when only editorSizeChanged fires but
539
+ // rootActiveNodeChanged is also true. So we use a separate, broader guard that
540
+ // includes rootActiveNodeChanged to avoid leaving the attribute on a stale root node.
541
+ var needsQuickInsertDecUpdate = activeNodeChanged || isActiveNodeModified || rootActiveNodeChanged;
542
+ if (needsQuickInsertDecUpdate && latestActiveNode.rootPos !== undefined) {
543
+ var _newState$doc$nodeAt2;
544
+ var rootNodeSize = (_newState$doc$nodeAt2 = newState.doc.nodeAt(latestActiveNode.rootPos)) === null || _newState$doc$nodeAt2 === void 0 ? void 0 : _newState$doc$nodeAt2.nodeSize;
545
+ if (rootNodeSize !== undefined) {
546
+ var _activeNode16, _activeNode17;
547
+ var _oldActiveQuickInsertDec = findActiveQuickInsertNodeDec(decorations, (_activeNode16 = activeNode) === null || _activeNode16 === void 0 ? void 0 : _activeNode16.rootPos, (_activeNode17 = activeNode) === null || _activeNode17 === void 0 ? void 0 : _activeNode17.rootPos);
548
+ decorations = decorations.remove(_oldActiveQuickInsertDec);
549
+ decorations = decorations.add(newState.doc, [createActiveQuickInsertNodeDecoration(latestActiveNode.rootPos, rootNodeSize)]);
550
+ }
551
+ }
552
+ }
510
553
  }
511
554
  if (shouldRecreateQuickInsertButton && ((_latestActiveNode5 = latestActiveNode) === null || _latestActiveNode5 === void 0 ? void 0 : _latestActiveNode5.rootPos) !== undefined &&
512
555
  // platform_editor_controls note: enables quick insert
513
556
  flags.toolbarFlagsEnabled && quickInsertButtonEnabled && (!rightSideControlsEnabled || !isViewMode)) {
514
- var _activeNode13, _activeNode14, _latestActiveNode6, _latestActiveNode7, _latestActiveNode8, _latestActiveNode9, _latestActiveNode0;
515
- var _oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode13 = activeNode) === null || _activeNode13 === void 0 ? void 0 : _activeNode13.rootPos, (_activeNode14 = activeNode) === null || _activeNode14 === void 0 ? void 0 : _activeNode14.rootPos);
557
+ var _activeNode18, _activeNode19, _latestActiveNode6, _latestActiveNode7, _latestActiveNode8, _latestActiveNode9, _latestActiveNode0, _latestActiveNode1;
558
+ var _oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode18 = activeNode) === null || _activeNode18 === void 0 ? void 0 : _activeNode18.rootPos, (_activeNode19 = activeNode) === null || _activeNode19 === void 0 ? void 0 : _activeNode19.rootPos);
516
559
  decorations = decorations.remove(_oldQuickInsertButton);
517
560
  var quickInsertButton = quickInsertButtonDecoration({
518
561
  api: api,
@@ -527,12 +570,27 @@ var _apply = function apply(api, formatMessage, tr, currentState, newState, flag
527
570
  editorState: newState
528
571
  });
529
572
  decorations = decorations.add(newState.doc, [quickInsertButton]);
573
+
574
+ // Update quick insert node decoration when the quick insert button is recreated but
575
+ // the drag handle was NOT recreated (shouldRecreateHandle was false). When
576
+ // shouldRecreateHandle is true, the block above already handles this.
577
+ if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && !shouldRecreateHandle && ((_latestActiveNode1 = latestActiveNode) === null || _latestActiveNode1 === void 0 ? void 0 : _latestActiveNode1.rootPos) !== undefined) {
578
+ var _activeNode20, _newState$doc$nodeAt3;
579
+ if (((_activeNode20 = activeNode) === null || _activeNode20 === void 0 ? void 0 : _activeNode20.rootPos) !== undefined) {
580
+ var _oldActiveQuickInsertDec2 = findActiveQuickInsertNodeDec(decorations, activeNode.rootPos, activeNode.rootPos);
581
+ decorations = decorations.remove(_oldActiveQuickInsertDec2);
582
+ }
583
+ var _rootNodeSize = (_newState$doc$nodeAt3 = newState.doc.nodeAt(latestActiveNode.rootPos)) === null || _newState$doc$nodeAt3 === void 0 ? void 0 : _newState$doc$nodeAt3.nodeSize;
584
+ if (_rootNodeSize !== undefined) {
585
+ decorations = decorations.add(newState.doc, [createActiveQuickInsertNodeDecoration(latestActiveNode.rootPos, _rootNodeSize)]);
586
+ }
587
+ }
530
588
  if (rightSideControlsEnabled) {
531
589
  var _iterator3 = _createForOfIteratorHelper(nodeDecorationRegistry),
532
590
  _step3;
533
591
  try {
534
592
  var _loop3 = function _loop3() {
535
- var _activeNode15, _activeNode16;
593
+ var _activeNode21, _activeNode22;
536
594
  var factory = _step3.value;
537
595
  if (!latestActiveNode || latestActiveNode.rootPos === undefined) {
538
596
  return 0; // continue
@@ -546,7 +604,7 @@ var _apply = function apply(api, formatMessage, tr, currentState, newState, flag
546
604
  rootAnchorName: latestActiveNode.rootAnchorName,
547
605
  rootNodeType: latestActiveNode.rootNodeType
548
606
  };
549
- 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) {
607
+ 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) {
550
608
  return spec.type === factory.type;
551
609
  });
552
610
  decorations = decorations.remove(old);
@@ -591,7 +649,7 @@ var _apply = function apply(api, formatMessage, tr, currentState, newState, flag
591
649
  }
592
650
 
593
651
  // In view mode (edit/live pages), show right-side controls on block hover (without drag handle or quick insert)
594
- var rootPos = (_latestActiveNode1 = latestActiveNode) === null || _latestActiveNode1 === void 0 ? void 0 : _latestActiveNode1.rootPos;
652
+ var rootPos = (_latestActiveNode10 = latestActiveNode) === null || _latestActiveNode10 === void 0 ? void 0 : _latestActiveNode10.rootPos;
595
653
  // rootPos is computed using the same logic as the floating insert menu, so it always points to a top-level (doc child) block when defined.
596
654
  var isDocLevel = rootPos !== undefined && !isNaN(rootPos);
597
655
  if (isViewMode && isDocLevel && flags.toolbarFlagsEnabled && rightSideControlsEnabled) {
@@ -712,15 +770,15 @@ var _apply = function apply(api, formatMessage, tr, currentState, newState, flag
712
770
  var newActiveNode;
713
771
  // platform_editor_controls note: enables quick insert
714
772
  if (flags.toolbarFlagsEnabled) {
715
- var _latestActiveNode10, _latestActiveNode11;
773
+ var _latestActiveNode11, _latestActiveNode12;
716
774
  // remove isEmptyDoc check and let decorations render and determine their own visibility
717
775
  // In view mode with right-side controls we render node decorations (right-edge button), not the
718
776
  // handle - so findHandleDec is always empty. Don't clear activeNode in that case.
719
- var hasHandleOrViewModeControls = findHandleDec(decorations, (_latestActiveNode10 = latestActiveNode) === null || _latestActiveNode10 === void 0 ? void 0 : _latestActiveNode10.pos, (_latestActiveNode11 = latestActiveNode) === null || _latestActiveNode11 === void 0 ? void 0 : _latestActiveNode11.pos).length > 0 || isViewMode && rightSideControlsEnabled;
777
+ var hasHandleOrViewModeControls = findHandleDec(decorations, (_latestActiveNode11 = latestActiveNode) === null || _latestActiveNode11 === void 0 ? void 0 : _latestActiveNode11.pos, (_latestActiveNode12 = latestActiveNode) === null || _latestActiveNode12 === void 0 ? void 0 : _latestActiveNode12.pos).length > 0 || isViewMode && rightSideControlsEnabled;
720
778
  newActiveNode = meta !== null && meta !== void 0 && meta.editorBlurred || !(meta !== null && meta !== void 0 && meta.activeNode) && !hasHandleOrViewModeControls ? null : latestActiveNode;
721
779
  } else {
722
- var _latestActiveNode12, _latestActiveNode13;
723
- newActiveNode = isEmptyDoc || !(meta !== null && meta !== void 0 && meta.activeNode) && findHandleDec(decorations, (_latestActiveNode12 = latestActiveNode) === null || _latestActiveNode12 === void 0 ? void 0 : _latestActiveNode12.pos, (_latestActiveNode13 = latestActiveNode) === null || _latestActiveNode13 === void 0 ? void 0 : _latestActiveNode13.pos).length === 0 ? null : latestActiveNode;
780
+ var _latestActiveNode13, _latestActiveNode14;
781
+ newActiveNode = isEmptyDoc || !(meta !== null && meta !== void 0 && meta.activeNode) && findHandleDec(decorations, (_latestActiveNode13 = latestActiveNode) === null || _latestActiveNode13 === void 0 ? void 0 : _latestActiveNode13.pos, (_latestActiveNode14 = latestActiveNode) === null || _latestActiveNode14 === void 0 ? void 0 : _latestActiveNode14.pos).length === 0 ? null : latestActiveNode;
724
782
  }
725
783
  var isMenuOpenNew = isMenuOpen;
726
784
  if (editorExperiment('platform_editor_block_menu', true)) {
@@ -5,6 +5,8 @@ import { breakoutResizableNodes as breakoutResizableNodesNew } from '@atlaskit/e
5
5
  import { akEditorUnitZIndex, akRichMediaResizeZIndex } from '@atlaskit/editor-shared-styles';
6
6
  import { fg } from '@atlaskit/platform-feature-flags';
7
7
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
8
+ export var ACTIVE_DRAG_HANDLE_ATTR = 'data-active-drag-handle';
9
+ export var ACTIVE_QUICK_INSERT_ATTR = 'data-active-quick-insert';
8
10
  export var DRAG_HANDLE_HEIGHT = 24;
9
11
  export var DRAG_HANDLE_BORDER_RADIUS = 4;
10
12
  export var DRAG_HANDLE_ZINDEX = akRichMediaResizeZIndex + akEditorUnitZIndex; //place above legacy resizer
@@ -40,7 +40,7 @@ import { getControlBottomCSSValue, getControlHeightCSSValue, getLeftPosition, ge
40
40
  import { expandAndUpdateSelection } from '../pm-plugins/utils/expand-and-update-selection';
41
41
  import { isHandleCorrelatedToSelection, selectNode } from '../pm-plugins/utils/getSelection';
42
42
  import { alignAnchorHeadInDirectionOfPos, expandSelectionHeadToNodeAtPos } from '../pm-plugins/utils/selection';
43
- import { DRAG_HANDLE_BORDER_RADIUS, DRAG_HANDLE_HEIGHT, DRAG_HANDLE_MAX_SHIFT_CLICK_DEPTH, DRAG_HANDLE_ZINDEX, dragHandleGap, nodeMargins, spacingBetweenNodesForPreview, STICKY_CONTROLS_TOP_MARGIN, STICKY_CONTROLS_TOP_MARGIN_FOR_STICKY_HEADER, topPositionAdjustment } from './consts';
43
+ import { ACTIVE_DRAG_HANDLE_ATTR, DRAG_HANDLE_BORDER_RADIUS, DRAG_HANDLE_HEIGHT, DRAG_HANDLE_MAX_SHIFT_CLICK_DEPTH, DRAG_HANDLE_ZINDEX, dragHandleGap, nodeMargins, spacingBetweenNodesForPreview, STICKY_CONTROLS_TOP_MARGIN, STICKY_CONTROLS_TOP_MARGIN_FOR_STICKY_HEADER, topPositionAdjustment } from './consts';
44
44
  import { DragHandleNestedIcon } from './drag-handle-nested-icon';
45
45
  import { dragPreview } from './drag-preview';
46
46
  import { refreshAnchorName } from './utils/anchor-name';
@@ -823,6 +823,15 @@ export var DragHandle = function DragHandle(_ref) {
823
823
  anchorName: anchorName
824
824
  }) : anchorName;
825
825
  var dom = view.dom.querySelector("[".concat(getAnchorAttrName(), "=\"").concat(safeAnchorName, "\"]"));
826
+
827
+ // Defence-in-depth guard: since the node decoration sets data-active-drag-handle on the
828
+ // active node, we check for it directly. This is a cheap DOM attribute read (no reflow,
829
+ // no style recalculation) and hides the control if the decoration hasn't been applied yet.
830
+ if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && !(dom !== null && dom !== void 0 && dom.hasAttribute(ACTIVE_DRAG_HANDLE_ATTR))) {
831
+ return {
832
+ display: 'none'
833
+ };
834
+ }
826
835
  var hasResizer = nodeType === 'table' || nodeType === 'mediaSingle';
827
836
  var isExtension = nodeType === 'extension' || nodeType === 'bodiedExtension' || nodeType === 'multiBodiedExtension' && fg('confluence_frontend_native_tabs_extension');
828
837
  var isBlockCard = nodeType === 'blockCard' && !!blockCardWidth;
@@ -12,7 +12,7 @@ import { ZERO_WIDTH_SPACE } from '@atlaskit/editor-common/whitespace';
12
12
  import { akEditorBreakoutPadding, akEditorCalculatedWideLayoutWidth, akEditorCalculatedWideLayoutWidthSmallViewport, akEditorFullPageNarrowBreakout, akEditorGutterPaddingDynamic, akEditorGutterPaddingReduced } from '@atlaskit/editor-shared-styles';
13
13
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
14
14
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
15
- import { DRAG_HANDLE_MAX_WIDTH_PLUS_GAP } from './consts';
15
+ import { ACTIVE_DRAG_HANDLE_ATTR, ACTIVE_QUICK_INSERT_ATTR, DRAG_HANDLE_MAX_WIDTH_PLUS_GAP } from './consts';
16
16
  import { NODE_ANCHOR_ATTR_NAME } from './utils/dom-attr-name';
17
17
 
18
18
  /**
@@ -356,6 +356,18 @@ var dragHandlerAnchorStyles = css({
356
356
  })
357
357
  }
358
358
  });
359
+ var activeControlsSelector = "[".concat(ACTIVE_DRAG_HANDLE_ATTR, "], [").concat(ACTIVE_QUICK_INSERT_ATTR, "]");
360
+
361
+ // Applies anchor-name via node decoration attributes rather than adjacency CSS selectors.
362
+ // This is more reliable than dragHandlerAnchorStyles which depends on DOM structure.
363
+ // Only nodes decorated with data-active-drag-handle / data-active-quick-insert get anchor-name.
364
+ var staticControlsAnchorStyles = css({
365
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
366
+ '.ProseMirror': _defineProperty({}, activeControlsSelector, {
367
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
368
+ anchorName: "var(".concat(ANCHOR_VARIABLE_NAME, ", attr(data-node-anchor type(<custom-ident>)))")
369
+ })
370
+ });
359
371
 
360
372
  // Styles applied to nodes with anchors when dragging
361
373
  var dragAnchorStyles = css({
@@ -404,6 +416,6 @@ export var GlobalStylesWrapper = function GlobalStylesWrapper(_ref) {
404
416
  exposure: true
405
417
  }) ? expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? extendHoverZoneReducedNext : extendHoverZoneReduced : undefined,
406
418
  // platform_editor_controls note: this allows drag handles to render on empty lines
407
- 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)]
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]
408
420
  });
409
421
  };
@@ -28,7 +28,7 @@ import Tooltip from '@atlaskit/tooltip';
28
28
  import { getNodeTypeWithLevel } from '../pm-plugins/decorations-common';
29
29
  import { getControlBottomCSSValue, getControlHeightCSSValue, getNodeHeight, getTopPosition, shouldBeSticky } from '../pm-plugins/utils/drag-handle-positions';
30
30
  import { getLeftPositionForRootElement } from '../pm-plugins/utils/widget-positions';
31
- import { QUICK_INSERT_DIMENSIONS, QUICK_INSERT_HEIGHT, QUICK_INSERT_LEFT_OFFSET, QUICK_INSERT_WIDTH, rootElementGap, STICKY_CONTROLS_TOP_MARGIN_FOR_STICKY_HEADER, topPositionAdjustment } from './consts';
31
+ import { ACTIVE_QUICK_INSERT_ATTR, QUICK_INSERT_DIMENSIONS, QUICK_INSERT_HEIGHT, QUICK_INSERT_LEFT_OFFSET, QUICK_INSERT_WIDTH, rootElementGap, STICKY_CONTROLS_TOP_MARGIN_FOR_STICKY_HEADER, topPositionAdjustment } from './consts';
32
32
  import { refreshAnchorName } from './utils/anchor-name';
33
33
  import { isInTextSelection, isNestedNodeSelected, isNonEditableBlock, isSelectionInNode } from './utils/document-checks';
34
34
  import { getAnchorAttrName } from './utils/dom-attr-name';
@@ -184,6 +184,14 @@ export var TypeAheadControl = function TypeAheadControl(_ref) {
184
184
  anchorName: rootAnchorName
185
185
  });
186
186
  var dom = view.dom.querySelector("[".concat(getAnchorAttrName(), "=\"").concat(safeAnchorName, "\"]"));
187
+
188
+ // Defence-in-depth guard: the node decoration sets data-active-quick-insert on the
189
+ // active root node. Check for it directly — cheap DOM attribute read, no reflow.
190
+ if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && !(dom !== null && dom !== void 0 && dom.hasAttribute(ACTIVE_QUICK_INSERT_ATTR))) {
191
+ return {
192
+ display: 'none'
193
+ };
194
+ }
187
195
  var hasResizer = rootNodeType === 'table' || rootNodeType === 'mediaSingle';
188
196
  var isExtension = rootNodeType === 'extension' || rootNodeType === 'bodiedExtension' || rootNodeType === 'multiBodiedExtension' && fg('confluence_frontend_native_tabs_extension');
189
197
  var isBlockCard = rootNodeType === 'blockCard';
@@ -6,6 +6,13 @@ import { Decoration } from '@atlaskit/editor-prosemirror/view';
6
6
  import type { DecorationSet } from '@atlaskit/editor-prosemirror/view';
7
7
  import type { BlockControlsPlugin, HandleOptions } from '../blockControlsPluginType';
8
8
  import type { AnchorRectCache } from './utils/anchor-utils';
9
+ /**
10
+ * Creates a Decoration.node that marks the active node with `data-active-drag-handle="true"`.
11
+ * The CSS in staticControlsAnchorStyles then applies `anchor-name` to this attribute,
12
+ * which is more reliable than the adjacency-selector approach in dragHandlerAnchorStyles.
13
+ */
14
+ export declare const createActiveDragHandleNodeDecoration: (pos: number, nodeSize: number) => Decoration;
15
+ export declare const findActiveDragHandleNodeDec: (decorations: DecorationSet, from?: number, to?: number) => Decoration[];
9
16
  export declare const emptyParagraphNodeDecorations: () => Decoration;
10
17
  export declare const findHandleDec: (decorations: DecorationSet, from?: number, to?: number) => Decoration[];
11
18
  type DragHandleDecorationParams = {
@@ -19,5 +19,12 @@ type QuickInsertButtonDecorationParams = {
19
19
  rootNodeType?: string;
20
20
  rootPos: number;
21
21
  };
22
+ /**
23
+ * Creates a Decoration.node that marks the active node with `data-active-quick-insert="true"`.
24
+ * The CSS in staticControlsAnchorStyles applies `anchor-name` to this attribute directly,
25
+ * replacing the unreliable adjacency selector `[block-ctrl-quick-insert-button] + *`.
26
+ */
27
+ export declare const createActiveQuickInsertNodeDecoration: (pos: number, nodeSize: number) => Decoration;
28
+ export declare const findActiveQuickInsertNodeDec: (decorations: DecorationSet, from?: number, to?: number) => Decoration[];
22
29
  export declare const quickInsertButtonDecoration: ({ api, formatMessage, rootPos, anchorName, nodeType, nodeViewPortalProviderAPI, rootAnchorName, rootNodeType, anchorRectCache, editorState, }: QuickInsertButtonDecorationParams) => Decoration;
23
30
  export {};
@@ -1,3 +1,5 @@
1
+ export declare const ACTIVE_DRAG_HANDLE_ATTR = "data-active-drag-handle";
2
+ export declare const ACTIVE_QUICK_INSERT_ATTR = "data-active-quick-insert";
1
3
  export declare const DRAG_HANDLE_HEIGHT = 24;
2
4
  export declare const DRAG_HANDLE_BORDER_RADIUS = 4;
3
5
  export declare const DRAG_HANDLE_ZINDEX: number;
@@ -6,6 +6,13 @@ import { Decoration } from '@atlaskit/editor-prosemirror/view';
6
6
  import type { DecorationSet } from '@atlaskit/editor-prosemirror/view';
7
7
  import type { BlockControlsPlugin, HandleOptions } from '../blockControlsPluginType';
8
8
  import type { AnchorRectCache } from './utils/anchor-utils';
9
+ /**
10
+ * Creates a Decoration.node that marks the active node with `data-active-drag-handle="true"`.
11
+ * The CSS in staticControlsAnchorStyles then applies `anchor-name` to this attribute,
12
+ * which is more reliable than the adjacency-selector approach in dragHandlerAnchorStyles.
13
+ */
14
+ export declare const createActiveDragHandleNodeDecoration: (pos: number, nodeSize: number) => Decoration;
15
+ export declare const findActiveDragHandleNodeDec: (decorations: DecorationSet, from?: number, to?: number) => Decoration[];
9
16
  export declare const emptyParagraphNodeDecorations: () => Decoration;
10
17
  export declare const findHandleDec: (decorations: DecorationSet, from?: number, to?: number) => Decoration[];
11
18
  type DragHandleDecorationParams = {
@@ -19,5 +19,12 @@ type QuickInsertButtonDecorationParams = {
19
19
  rootNodeType?: string;
20
20
  rootPos: number;
21
21
  };
22
+ /**
23
+ * Creates a Decoration.node that marks the active node with `data-active-quick-insert="true"`.
24
+ * The CSS in staticControlsAnchorStyles applies `anchor-name` to this attribute directly,
25
+ * replacing the unreliable adjacency selector `[block-ctrl-quick-insert-button] + *`.
26
+ */
27
+ export declare const createActiveQuickInsertNodeDecoration: (pos: number, nodeSize: number) => Decoration;
28
+ export declare const findActiveQuickInsertNodeDec: (decorations: DecorationSet, from?: number, to?: number) => Decoration[];
22
29
  export declare const quickInsertButtonDecoration: ({ api, formatMessage, rootPos, anchorName, nodeType, nodeViewPortalProviderAPI, rootAnchorName, rootNodeType, anchorRectCache, editorState, }: QuickInsertButtonDecorationParams) => Decoration;
23
30
  export {};
@@ -1,3 +1,5 @@
1
+ export declare const ACTIVE_DRAG_HANDLE_ATTR = "data-active-drag-handle";
2
+ export declare const ACTIVE_QUICK_INSERT_ATTR = "data-active-quick-insert";
1
3
  export declare const DRAG_HANDLE_HEIGHT = 24;
2
4
  export declare const DRAG_HANDLE_BORDER_RADIUS = 4;
3
5
  export declare const DRAG_HANDLE_ZINDEX: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-block-controls",
3
- "version": "12.1.1",
3
+ "version": "12.2.0",
4
4
  "description": "Block controls plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -56,7 +56,7 @@
56
56
  "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^3.2.0",
57
57
  "@atlaskit/primitives": "^19.0.0",
58
58
  "@atlaskit/theme": "^25.0.0",
59
- "@atlaskit/tmp-editor-statsig": "^90.0.0",
59
+ "@atlaskit/tmp-editor-statsig": "^90.1.0",
60
60
  "@atlaskit/tokens": "^13.3.0",
61
61
  "@atlaskit/tooltip": "^22.6.0",
62
62
  "@babel/runtime": "^7.0.0",