@atlaskit/editor-plugin-block-controls 12.4.1 → 13.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/block-decoration-utils/anchor-name/package.json +1 -8
  3. package/block-decoration-utils/consts/package.json +1 -8
  4. package/block-decoration-utils/dom-attr-name/package.json +1 -8
  5. package/block-decoration-utils/drag-handle-positions/package.json +1 -8
  6. package/block-decoration-utils/package.json +1 -8
  7. package/block-decoration-utils/visibility-container/package.json +1 -8
  8. package/block-decoration-utils/widget-positions/package.json +1 -8
  9. package/blockControlsPlugin/package.json +1 -8
  10. package/blockControlsPluginType/package.json +1 -8
  11. package/dist/cjs/blockControlsPlugin.js +1 -1
  12. package/dist/cjs/pm-plugins/interaction-tracking/constants.js +8 -0
  13. package/dist/cjs/pm-plugins/interaction-tracking/handle-mouse-move.js +102 -14
  14. package/dist/cjs/pm-plugins/interaction-tracking/pm-plugin.js +44 -10
  15. package/dist/cjs/ui/visibility-container.js +2 -0
  16. package/dist/es2019/blockControlsPlugin.js +1 -1
  17. package/dist/es2019/pm-plugins/interaction-tracking/constants.js +2 -0
  18. package/dist/es2019/pm-plugins/interaction-tracking/handle-mouse-move.js +86 -13
  19. package/dist/es2019/pm-plugins/interaction-tracking/pm-plugin.js +44 -11
  20. package/dist/es2019/ui/visibility-container.js +2 -0
  21. package/dist/esm/blockControlsPlugin.js +1 -1
  22. package/dist/esm/pm-plugins/interaction-tracking/constants.js +2 -0
  23. package/dist/esm/pm-plugins/interaction-tracking/handle-mouse-move.js +102 -14
  24. package/dist/esm/pm-plugins/interaction-tracking/pm-plugin.js +44 -10
  25. package/dist/esm/ui/visibility-container.js +2 -0
  26. package/dist/types/pm-plugins/interaction-tracking/constants.d.ts +2 -0
  27. package/dist/types/pm-plugins/interaction-tracking/handle-mouse-move.d.ts +3 -1
  28. package/dist/types/pm-plugins/interaction-tracking/pm-plugin.d.ts +3 -1
  29. package/package.json +37 -41
  30. package/dist/types-ts4.5/blockControlsPlugin.d.ts +0 -2
  31. package/dist/types-ts4.5/blockControlsPluginType.d.ts +0 -250
  32. package/dist/types-ts4.5/editor-commands/handle-key-down-with-preserved-selection.d.ts +0 -17
  33. package/dist/types-ts4.5/editor-commands/map-preserved-selection.d.ts +0 -13
  34. package/dist/types-ts4.5/editor-commands/move-node-with-block-menu.d.ts +0 -4
  35. package/dist/types-ts4.5/editor-commands/move-node.d.ts +0 -5
  36. package/dist/types-ts4.5/editor-commands/move-to-layout.d.ts +0 -7
  37. package/dist/types-ts4.5/editor-commands/show-drag-handle.d.ts +0 -3
  38. package/dist/types-ts4.5/editor-commands/utils/move-node-utils.d.ts +0 -31
  39. package/dist/types-ts4.5/entry-points/block-decoration-utils-anchor-name.d.ts +0 -1
  40. package/dist/types-ts4.5/entry-points/block-decoration-utils-consts.d.ts +0 -1
  41. package/dist/types-ts4.5/entry-points/block-decoration-utils-dom-attr-name.d.ts +0 -1
  42. package/dist/types-ts4.5/entry-points/block-decoration-utils-drag-handle-positions.d.ts +0 -1
  43. package/dist/types-ts4.5/entry-points/block-decoration-utils-visibility-container.d.ts +0 -1
  44. package/dist/types-ts4.5/entry-points/block-decoration-utils-widget-positions.d.ts +0 -1
  45. package/dist/types-ts4.5/entry-points/blockControlsPlugin.d.ts +0 -1
  46. package/dist/types-ts4.5/entry-points/blockControlsPluginType.d.ts +0 -1
  47. package/dist/types-ts4.5/index.d.ts +0 -2
  48. package/dist/types-ts4.5/pm-plugins/decorations-anchor.d.ts +0 -15
  49. package/dist/types-ts4.5/pm-plugins/decorations-common.d.ts +0 -9
  50. package/dist/types-ts4.5/pm-plugins/decorations-drag-handle.d.ts +0 -30
  51. package/dist/types-ts4.5/pm-plugins/decorations-drop-target-active.d.ts +0 -13
  52. package/dist/types-ts4.5/pm-plugins/decorations-drop-target.d.ts +0 -21
  53. package/dist/types-ts4.5/pm-plugins/decorations-find-surrounding-nodes.d.ts +0 -22
  54. package/dist/types-ts4.5/pm-plugins/decorations-quick-insert-button.d.ts +0 -30
  55. package/dist/types-ts4.5/pm-plugins/first-node-dec-plugin.d.ts +0 -5
  56. package/dist/types-ts4.5/pm-plugins/handle-mouse-down.d.ts +0 -4
  57. package/dist/types-ts4.5/pm-plugins/handle-mouse-over.d.ts +0 -4
  58. package/dist/types-ts4.5/pm-plugins/interaction-tracking/commands.d.ts +0 -7
  59. package/dist/types-ts4.5/pm-plugins/interaction-tracking/handle-key-down.d.ts +0 -2
  60. package/dist/types-ts4.5/pm-plugins/interaction-tracking/handle-mouse-move.d.ts +0 -4
  61. package/dist/types-ts4.5/pm-plugins/interaction-tracking/pm-plugin.d.ts +0 -20
  62. package/dist/types-ts4.5/pm-plugins/keymap.d.ts +0 -5
  63. package/dist/types-ts4.5/pm-plugins/main.d.ts +0 -63
  64. package/dist/types-ts4.5/pm-plugins/quick-insert-calculate-position.d.ts +0 -12
  65. package/dist/types-ts4.5/pm-plugins/selection-preservation/editor-commands.d.ts +0 -13
  66. package/dist/types-ts4.5/pm-plugins/selection-preservation/plugin-key.d.ts +0 -3
  67. package/dist/types-ts4.5/pm-plugins/selection-preservation/pm-plugin.d.ts +0 -35
  68. package/dist/types-ts4.5/pm-plugins/selection-preservation/types.d.ts +0 -7
  69. package/dist/types-ts4.5/pm-plugins/selection-preservation/utils.d.ts +0 -30
  70. package/dist/types-ts4.5/pm-plugins/utils/active-anchor-tracker.d.ts +0 -16
  71. package/dist/types-ts4.5/pm-plugins/utils/analytics.d.ts +0 -12
  72. package/dist/types-ts4.5/pm-plugins/utils/anchor-utils.d.ts +0 -27
  73. package/dist/types-ts4.5/pm-plugins/utils/check-fragment.d.ts +0 -9
  74. package/dist/types-ts4.5/pm-plugins/utils/check-media-layout.d.ts +0 -2
  75. package/dist/types-ts4.5/pm-plugins/utils/consts.d.ts +0 -2
  76. package/dist/types-ts4.5/pm-plugins/utils/drag-handle-positions.d.ts +0 -12
  77. package/dist/types-ts4.5/pm-plugins/utils/expand-and-update-selection.d.ts +0 -20
  78. package/dist/types-ts4.5/pm-plugins/utils/getNestedNodePosition.d.ts +0 -16
  79. package/dist/types-ts4.5/pm-plugins/utils/getSelection.d.ts +0 -32
  80. package/dist/types-ts4.5/pm-plugins/utils/inline-drop-target.d.ts +0 -7
  81. package/dist/types-ts4.5/pm-plugins/utils/marks.d.ts +0 -25
  82. package/dist/types-ts4.5/pm-plugins/utils/remove-from-source.d.ts +0 -3
  83. package/dist/types-ts4.5/pm-plugins/utils/selection.d.ts +0 -71
  84. package/dist/types-ts4.5/pm-plugins/utils/transactions.d.ts +0 -31
  85. package/dist/types-ts4.5/pm-plugins/utils/update-column-widths.d.ts +0 -6
  86. package/dist/types-ts4.5/pm-plugins/utils/update-selection.d.ts +0 -4
  87. package/dist/types-ts4.5/pm-plugins/utils/validation.d.ts +0 -24
  88. package/dist/types-ts4.5/pm-plugins/utils/widget-positions.d.ts +0 -14
  89. package/dist/types-ts4.5/pm-plugins/vanilla-quick-insert.d.ts +0 -21
  90. package/dist/types-ts4.5/ui/block-decoration-utils.d.ts +0 -6
  91. package/dist/types-ts4.5/ui/consts.d.ts +0 -67
  92. package/dist/types-ts4.5/ui/drag-handle-nested-icon.d.ts +0 -12
  93. package/dist/types-ts4.5/ui/drag-handle.d.ts +0 -20
  94. package/dist/types-ts4.5/ui/drag-preview.d.ts +0 -9
  95. package/dist/types-ts4.5/ui/drop-target-layout.d.ts +0 -18
  96. package/dist/types-ts4.5/ui/drop-target.d.ts +0 -22
  97. package/dist/types-ts4.5/ui/global-styles.d.ts +0 -10
  98. package/dist/types-ts4.5/ui/inline-drop-target.d.ts +0 -7
  99. package/dist/types-ts4.5/ui/quick-insert-button.d.ts +0 -24
  100. package/dist/types-ts4.5/ui/utils/anchor-name.d.ts +0 -12
  101. package/dist/types-ts4.5/ui/utils/document-checks.d.ts +0 -5
  102. package/dist/types-ts4.5/ui/utils/dom-attr-name.d.ts +0 -5
  103. package/dist/types-ts4.5/ui/utils/editor-commands.d.ts +0 -2
  104. package/dist/types-ts4.5/ui/visibility-container.d.ts +0 -16
@@ -2,15 +2,42 @@ import { bind } from 'bind-event-listener';
2
2
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
3
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
4
4
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
5
+ import { RIGHT_MARGIN_ROVO_GAP_PX } from './constants';
5
6
  import { handleKeyDown } from './handle-key-down';
6
7
  import { handleMouseEnter, handleMouseLeave, handleMouseMove } from './handle-mouse-move';
7
8
 
8
9
  /** Elements that extend the editor hover area (block controls, right-edge button, etc.) */
9
10
  const BLOCK_CONTROLS_HOVER_AREA_SELECTOR = '[data-blocks-right-edge-button-container], [data-blocks-drag-handle-container], [data-testid="block-ctrl-drag-handle"], [data-testid="block-ctrl-drag-handle-container"], [data-testid="block-ctrl-decorator-widget"], [data-testid="block-ctrl-quick-insert-button"]';
10
11
  const MOUSE_LEAVE_DEBOUNCE_MS = 200;
12
+
13
+ /** ClickAreaBlock overlay that wraps the editor content and covers the right margin. */
14
+ const CLICK_AREA_SELECTOR = '[data-editor-click-wrapper]';
11
15
  const isMovingToBlockControlsArea = target => target instanceof Element && !!target.closest(BLOCK_CONTROLS_HOVER_AREA_SELECTOR);
16
+
17
+ /**
18
+ * The right margin is covered by the ClickAreaBlock overlay, which sits outside .ak-editor-content-area.
19
+ * Hovering there should still surface the right-side Remix button, so keep controls alive — but only on
20
+ * the right (past the content's right edge), and not in the far-right Rovo gap. The left gutter must
21
+ * dismiss like the experiment-off path, so it is treated as inactive.
22
+ */
23
+ const isOverActiveClickArea = (target, clientX) => {
24
+ var _clickArea$querySelec, _target$ownerDocument, _target$ownerDocument2;
25
+ if (!(target instanceof Element)) {
26
+ return false;
27
+ }
28
+ const clickArea = target.closest(CLICK_AREA_SELECTOR);
29
+ if (!clickArea) {
30
+ return false;
31
+ }
32
+ const contentRight = (_clickArea$querySelec = clickArea.querySelector('.ak-editor-content-area')) === null || _clickArea$querySelec === void 0 ? void 0 : _clickArea$querySelec.getBoundingClientRect().right;
33
+ if (contentRight !== undefined && clientX <= contentRight) {
34
+ return false;
35
+ }
36
+ const innerWidth = (_target$ownerDocument = (_target$ownerDocument2 = target.ownerDocument.defaultView) === null || _target$ownerDocument2 === void 0 ? void 0 : _target$ownerDocument2.innerWidth) !== null && _target$ownerDocument !== void 0 ? _target$ownerDocument : Number.POSITIVE_INFINITY;
37
+ return clientX <= innerWidth - RIGHT_MARGIN_ROVO_GAP_PX;
38
+ };
12
39
  export const interactionTrackingPluginKey = new PluginKey('interactionTrackingPlugin');
13
- export const createInteractionTrackingPlugin = (rightSideControlsEnabled = false) => {
40
+ export const createInteractionTrackingPlugin = (rightSideControlsEnabled = false, api) => {
14
41
  return new SafePlugin({
15
42
  key: interactionTrackingPluginKey,
16
43
  state: {
@@ -56,7 +83,7 @@ export const createInteractionTrackingPlugin = (rightSideControlsEnabled = false
56
83
  props: {
57
84
  handleKeyDown,
58
85
  handleDOMEvents: {
59
- mousemove: (view, event) => handleMouseMove(view, event, rightSideControlsEnabled)
86
+ mousemove: (view, event) => handleMouseMove(view, event, rightSideControlsEnabled, api)
60
87
  }
61
88
  },
62
89
  view: editorExperiment('platform_editor_controls', 'variant1') ? view => {
@@ -71,22 +98,28 @@ export const createInteractionTrackingPlugin = (rightSideControlsEnabled = false
71
98
  x: 0,
72
99
  y: 0
73
100
  };
101
+
102
+ // The active right margin only counts as "still hovering" when our experiment is on;
103
+ // otherwise leaving the content area (e.g. exiting left) must dismiss as on master.
104
+ const marginHoverEnabled = editorExperiment('remix_button_right_margin_hover', true);
74
105
  const scheduleMouseLeave = event => {
75
106
  if (mouseLeaveTimeoutId) {
76
107
  clearTimeout(mouseLeaveTimeoutId);
77
108
  mouseLeaveTimeoutId = null;
78
109
  }
79
110
 
80
- // Don't set isMouseOut when moving to block controls (right-edge button, drag handle, etc.)
81
- if (rightSideControlsEnabled && isMovingToBlockControlsArea(event.relatedTarget)) {
111
+ // Keep controls visible when moving to block controls (or, with the experiment on,
112
+ // the active right margin — the Rovo gap is excluded so controls still clear there).
113
+ if (rightSideControlsEnabled && (isMovingToBlockControlsArea(event.relatedTarget) || marginHoverEnabled && isOverActiveClickArea(event.relatedTarget, event.clientX))) {
82
114
  return;
83
115
  }
84
116
  mouseLeaveTimeoutId = setTimeout(() => {
85
117
  mouseLeaveTimeoutId = null;
86
- // Before dispatching, check if mouse has moved to block controls (e.g. through empty space)
118
+ // Re-check after the debounce: keep controls if the cursor landed on block controls
119
+ // (or, with the experiment on, the active right margin).
87
120
  if (rightSideControlsEnabled && typeof document !== 'undefined') {
88
121
  const el = document.elementFromPoint(lastMousePosition.x, lastMousePosition.y);
89
- if (el && isMovingToBlockControlsArea(el)) {
122
+ if (el && (isMovingToBlockControlsArea(el) || marginHoverEnabled && isOverActiveClickArea(el, lastMousePosition.x))) {
90
123
  return;
91
124
  }
92
125
  }
@@ -108,11 +141,11 @@ export const createInteractionTrackingPlugin = (rightSideControlsEnabled = false
108
141
  x: event.clientX,
109
142
  y: event.clientY
110
143
  };
111
- // Use document-level mousemove so we get events when hovering over block
112
- // controls (which may be in portals outside the editor DOM). Without this,
113
- // handleDOMEvents.mousemove only fires when over the editor content.
114
- if (editorContentArea.contains(event.target) || isMovingToBlockControlsArea(event.target)) {
115
- handleMouseMove(view, event, rightSideControlsEnabled);
144
+ // Catches block controls in portals that handleDOMEvents.mousemove misses.
145
+ // The right-margin overlay is only relevant with the experiment on.
146
+ const overClickArea = marginHoverEnabled && event.target instanceof Element && !!event.target.closest(CLICK_AREA_SELECTOR);
147
+ if (editorContentArea.contains(event.target) || isMovingToBlockControlsArea(event.target) || overClickArea) {
148
+ handleMouseMove(view, event, rightSideControlsEnabled, api);
116
149
  }
117
150
  },
118
151
  options: {
@@ -68,6 +68,8 @@ export const VisibilityContainer = ({
68
68
  });
69
69
  const isViewMode = editorViewMode === 'view';
70
70
  // rightSideControlsEnabled is the single source of truth (confluence_remix_button_right_side_block_fg from preset)
71
+ // Both controls are restricted by side: the drag handle shows on the left half, the Remix button on
72
+ // the right half (and the right margin), matching the midpoint split in handle-mouse-move.
71
73
  const shouldRestrictBySide = rightSideControlsEnabled && controlSide !== undefined && !isViewMode;
72
74
  // Only restrict by side when hoverSide is known (after mousemove). When undefined, show both
73
75
  // controls so drag handle is visible on load and for keyboard-only users.
@@ -83,7 +83,7 @@ export var blockControlsPlugin = function blockControlsPlugin(_ref) {
83
83
  pmPlugins.push({
84
84
  name: 'blockControlsInteractionTrackingPlugin',
85
85
  plugin: function plugin() {
86
- return createInteractionTrackingPlugin(rightSideControlsEnabled);
86
+ return createInteractionTrackingPlugin(rightSideControlsEnabled, api);
87
87
  }
88
88
  });
89
89
  }
@@ -0,0 +1,2 @@
1
+ /** Far-right margin width reserved for the Rovo button, where the Remix button must not show. */
2
+ export var RIGHT_MARGIN_ROVO_GAP_PX = 80;
@@ -1,4 +1,10 @@
1
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
2
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
3
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
4
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
5
+ import { handleMouseOver } from '../handle-mouse-over';
1
6
  import { clearHoverSide, mouseEnter, mouseLeave, setHoverSide, stopEditing } from './commands';
7
+ import { RIGHT_MARGIN_ROVO_GAP_PX } from './constants';
2
8
  import { getInteractionTrackingState } from './pm-plugin';
3
9
 
4
10
  /** Per-view pending hover state; avoids cross-editor singleton. */
@@ -20,11 +26,67 @@ var clearPendingHoverSide = function clearPendingHoverSide(view) {
20
26
  var BLOCK_SELECTORS = '[data-node-anchor], [data-drag-handler-anchor-name]';
21
27
  var RIGHT_EDGE_SELECTOR = '[data-blocks-right-edge-button-container]';
22
28
 
29
+ // Top-level blocks (no block ancestor), matched by anchor attribute so it works under both the
30
+ // legacy and native-anchor schemes.
31
+ var getRootBlocks = function getRootBlocks(view) {
32
+ return Array.from(view.dom.querySelectorAll(BLOCK_SELECTORS)).filter(function (el) {
33
+ var _el$parentElement;
34
+ return !((_el$parentElement = el.parentElement) !== null && _el$parentElement !== void 0 && _el$parentElement.closest(BLOCK_SELECTORS));
35
+ });
36
+ };
37
+
38
+ // Find the root block whose vertical bounds contain clientY. Returns the measured rect so callers
39
+ // can reuse it without re-measuring.
40
+ var findBlockAtY = function findBlockAtY(view, clientY) {
41
+ var _iterator = _createForOfIteratorHelper(getRootBlocks(view)),
42
+ _step;
43
+ try {
44
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
45
+ var el = _step.value;
46
+ var rect = el.getBoundingClientRect();
47
+ if (clientY >= rect.top && clientY <= rect.bottom) {
48
+ return {
49
+ block: el,
50
+ rect: rect
51
+ };
52
+ }
53
+ }
54
+ } catch (err) {
55
+ _iterator.e(err);
56
+ } finally {
57
+ _iterator.f();
58
+ }
59
+ return null;
60
+ };
61
+ var getRightMarginBoundary = function getRightMarginBoundary(view) {
62
+ var _view$dom$ownerDocume, _view$dom$ownerDocume2;
63
+ return ((_view$dom$ownerDocume = (_view$dom$ownerDocume2 = view.dom.ownerDocument.defaultView) === null || _view$dom$ownerDocume2 === void 0 ? void 0 : _view$dom$ownerDocume2.innerWidth) !== null && _view$dom$ownerDocume !== void 0 ? _view$dom$ownerDocume : Number.POSITIVE_INFINITY) - RIGHT_MARGIN_ROVO_GAP_PX;
64
+ };
65
+ // not in the right margin
66
+
67
+ // Classify where the cursor sits in the right margin beside the block at its height, running the
68
+ // block lookup once so the caller doesn't traverse the DOM twice per mousemove.
69
+ var classifyRightMarginPosition = function classifyRightMarginPosition(view, event) {
70
+ var found = findBlockAtY(view, event.clientY);
71
+ if (!found) {
72
+ return null;
73
+ }
74
+ if (event.clientX <= found.rect.right) {
75
+ return null;
76
+ }
77
+ return event.clientX > getRightMarginBoundary(view) ? {
78
+ type: 'gap'
79
+ } : {
80
+ type: 'active',
81
+ block: found.block
82
+ };
83
+ };
84
+
23
85
  /**
24
86
  * Process hover position and set left/right side. Only invoked when right-side controls are
25
87
  * enabled (confluence_remix_button_right_side_block_fg); handleMouseMove returns early otherwise.
26
88
  */
27
- var processHoverSide = function processHoverSide(view) {
89
+ var processHoverSide = function processHoverSide(view, api) {
28
90
  var event = pendingByView.get(view);
29
91
  if (!event) {
30
92
  return;
@@ -55,20 +117,45 @@ var processHoverSide = function processHoverSide(view) {
55
117
  return;
56
118
  }
57
119
 
58
- // Primary path: depth-1 block (doc direct child). Decorations-anchor sets [data-drag-handler-anchor-depth="1"]
59
- // on every root block (table, layoutSection, expand, etc.), so we get the whole block without per-type logic.
60
- var blockElement = target === null || target === void 0 ? void 0 : target.closest(BLOCK_SELECTORS);
61
- var depth1Block = blockElement instanceof HTMLElement ? blockElement.closest('[data-drag-handler-anchor-depth="1"]') : null;
62
- var boundsElement = depth1Block instanceof HTMLElement ? depth1Block : editorContentArea;
63
- if (!boundsElement) {
64
- if ((state === null || state === void 0 ? void 0 : state.hoverSide) !== undefined) {
65
- clearHoverSide(view);
120
+ // Added right-margin hover, gated so it can be rolled back. When off, fall through to midpoint.
121
+ if (editorExperiment('remix_button_right_margin_hover', true)) {
122
+ var closestBlock = target === null || target === void 0 ? void 0 : target.closest(BLOCK_SELECTORS);
123
+ var blockElement = closestBlock instanceof HTMLElement ? closestBlock : null;
124
+
125
+ // Not over a block: the cursor may be in the right margin beside content.
126
+ var marginZone = blockElement ? null : classifyRightMarginPosition(view, event);
127
+ if ((marginZone === null || marginZone === void 0 ? void 0 : marginZone.type) === 'active' && api) {
128
+ // handleMouseOver only reads event.target, so a target-only stand-in is enough here.
129
+ handleMouseOver(view, {
130
+ target: marginZone.block
131
+ }, api);
132
+ // mouseenter doesn't fire over the click overlay, so clear isMouseOut here to re-show.
133
+ if (state !== null && state !== void 0 && state.isMouseOut) {
134
+ mouseEnter(view);
135
+ }
136
+ if ((state === null || state === void 0 ? void 0 : state.hoverSide) !== 'right') {
137
+ setHoverSide(view, 'right');
138
+ }
139
+ return;
66
140
  }
67
- return;
141
+
142
+ // In the Rovo gap, dismiss the controls so the button doesn't linger over the Rovo button.
143
+ if ((marginZone === null || marginZone === void 0 ? void 0 : marginZone.type) === 'gap' || event.clientX > getRightMarginBoundary(view)) {
144
+ if (!(state !== null && state !== void 0 && state.isMouseOut)) {
145
+ clearHoverSide(view);
146
+ mouseLeave(view);
147
+ }
148
+ return;
149
+ }
150
+
151
+ // Over a block: fall through to the midpoint split so the left half keeps the drag handle and
152
+ // the right half shows the Remix button, matching the experiment-off behaviour.
68
153
  }
69
- var _boundsElement$getBou = boundsElement.getBoundingClientRect(),
70
- left = _boundsElement$getBou.left,
71
- right = _boundsElement$getBou.right;
154
+
155
+ // Pick the side from the content midpoint, keeping the original left/right halves.
156
+ var _editorContentArea$ge = editorContentArea.getBoundingClientRect(),
157
+ left = _editorContentArea$ge.left,
158
+ right = _editorContentArea$ge.right;
72
159
  var midpoint = (left + right) / 2;
73
160
  var nextHoverSide = event.clientX > midpoint ? 'right' : 'left';
74
161
  if ((state === null || state === void 0 ? void 0 : state.hoverSide) !== nextHoverSide) {
@@ -77,6 +164,7 @@ var processHoverSide = function processHoverSide(view) {
77
164
  };
78
165
  export var handleMouseMove = function handleMouseMove(view, event) {
79
166
  var rightSideControlsEnabled = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
167
+ var api = arguments.length > 3 ? arguments[3] : undefined;
80
168
  var state = getInteractionTrackingState(view.state);
81
169
  // if user has stopped editing and moved their mouse, show block controls again
82
170
  if (state !== null && state !== void 0 && state.isEditing) {
@@ -93,7 +181,7 @@ export var handleMouseMove = function handleMouseMove(view, event) {
93
181
  pendingByView.set(view, event);
94
182
  cancelScheduledProcessForView(view);
95
183
  var id = requestAnimationFrame(function () {
96
- processHoverSide(view);
184
+ processHoverSide(view, api);
97
185
  });
98
186
  rafIdByView.set(view, id);
99
187
  return false;
@@ -5,18 +5,46 @@ import { bind } from 'bind-event-listener';
5
5
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
6
6
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
7
7
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
8
+ import { RIGHT_MARGIN_ROVO_GAP_PX } from './constants';
8
9
  import { handleKeyDown } from './handle-key-down';
9
10
  import { handleMouseEnter, handleMouseLeave, handleMouseMove } from './handle-mouse-move';
10
11
 
11
12
  /** Elements that extend the editor hover area (block controls, right-edge button, etc.) */
12
13
  var BLOCK_CONTROLS_HOVER_AREA_SELECTOR = '[data-blocks-right-edge-button-container], [data-blocks-drag-handle-container], [data-testid="block-ctrl-drag-handle"], [data-testid="block-ctrl-drag-handle-container"], [data-testid="block-ctrl-decorator-widget"], [data-testid="block-ctrl-quick-insert-button"]';
13
14
  var MOUSE_LEAVE_DEBOUNCE_MS = 200;
15
+
16
+ /** ClickAreaBlock overlay that wraps the editor content and covers the right margin. */
17
+ var CLICK_AREA_SELECTOR = '[data-editor-click-wrapper]';
14
18
  var isMovingToBlockControlsArea = function isMovingToBlockControlsArea(target) {
15
19
  return target instanceof Element && !!target.closest(BLOCK_CONTROLS_HOVER_AREA_SELECTOR);
16
20
  };
21
+
22
+ /**
23
+ * The right margin is covered by the ClickAreaBlock overlay, which sits outside .ak-editor-content-area.
24
+ * Hovering there should still surface the right-side Remix button, so keep controls alive — but only on
25
+ * the right (past the content's right edge), and not in the far-right Rovo gap. The left gutter must
26
+ * dismiss like the experiment-off path, so it is treated as inactive.
27
+ */
28
+ var isOverActiveClickArea = function isOverActiveClickArea(target, clientX) {
29
+ var _clickArea$querySelec, _target$ownerDocument, _target$ownerDocument2;
30
+ if (!(target instanceof Element)) {
31
+ return false;
32
+ }
33
+ var clickArea = target.closest(CLICK_AREA_SELECTOR);
34
+ if (!clickArea) {
35
+ return false;
36
+ }
37
+ var contentRight = (_clickArea$querySelec = clickArea.querySelector('.ak-editor-content-area')) === null || _clickArea$querySelec === void 0 ? void 0 : _clickArea$querySelec.getBoundingClientRect().right;
38
+ if (contentRight !== undefined && clientX <= contentRight) {
39
+ return false;
40
+ }
41
+ var innerWidth = (_target$ownerDocument = (_target$ownerDocument2 = target.ownerDocument.defaultView) === null || _target$ownerDocument2 === void 0 ? void 0 : _target$ownerDocument2.innerWidth) !== null && _target$ownerDocument !== void 0 ? _target$ownerDocument : Number.POSITIVE_INFINITY;
42
+ return clientX <= innerWidth - RIGHT_MARGIN_ROVO_GAP_PX;
43
+ };
17
44
  export var interactionTrackingPluginKey = new PluginKey('interactionTrackingPlugin');
18
45
  export var createInteractionTrackingPlugin = function createInteractionTrackingPlugin() {
19
46
  var rightSideControlsEnabled = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
47
+ var api = arguments.length > 1 ? arguments[1] : undefined;
20
48
  return new SafePlugin({
21
49
  key: interactionTrackingPluginKey,
22
50
  state: {
@@ -60,7 +88,7 @@ export var createInteractionTrackingPlugin = function createInteractionTrackingP
60
88
  handleKeyDown: handleKeyDown,
61
89
  handleDOMEvents: {
62
90
  mousemove: function mousemove(view, event) {
63
- return handleMouseMove(view, event, rightSideControlsEnabled);
91
+ return handleMouseMove(view, event, rightSideControlsEnabled, api);
64
92
  }
65
93
  }
66
94
  },
@@ -76,22 +104,28 @@ export var createInteractionTrackingPlugin = function createInteractionTrackingP
76
104
  x: 0,
77
105
  y: 0
78
106
  };
107
+
108
+ // The active right margin only counts as "still hovering" when our experiment is on;
109
+ // otherwise leaving the content area (e.g. exiting left) must dismiss as on master.
110
+ var marginHoverEnabled = editorExperiment('remix_button_right_margin_hover', true);
79
111
  var scheduleMouseLeave = function scheduleMouseLeave(event) {
80
112
  if (mouseLeaveTimeoutId) {
81
113
  clearTimeout(mouseLeaveTimeoutId);
82
114
  mouseLeaveTimeoutId = null;
83
115
  }
84
116
 
85
- // Don't set isMouseOut when moving to block controls (right-edge button, drag handle, etc.)
86
- if (rightSideControlsEnabled && isMovingToBlockControlsArea(event.relatedTarget)) {
117
+ // Keep controls visible when moving to block controls (or, with the experiment on,
118
+ // the active right margin — the Rovo gap is excluded so controls still clear there).
119
+ if (rightSideControlsEnabled && (isMovingToBlockControlsArea(event.relatedTarget) || marginHoverEnabled && isOverActiveClickArea(event.relatedTarget, event.clientX))) {
87
120
  return;
88
121
  }
89
122
  mouseLeaveTimeoutId = setTimeout(function () {
90
123
  mouseLeaveTimeoutId = null;
91
- // Before dispatching, check if mouse has moved to block controls (e.g. through empty space)
124
+ // Re-check after the debounce: keep controls if the cursor landed on block controls
125
+ // (or, with the experiment on, the active right margin).
92
126
  if (rightSideControlsEnabled && typeof document !== 'undefined') {
93
127
  var el = document.elementFromPoint(lastMousePosition.x, lastMousePosition.y);
94
- if (el && isMovingToBlockControlsArea(el)) {
128
+ if (el && (isMovingToBlockControlsArea(el) || marginHoverEnabled && isOverActiveClickArea(el, lastMousePosition.x))) {
95
129
  return;
96
130
  }
97
131
  }
@@ -113,11 +147,11 @@ export var createInteractionTrackingPlugin = function createInteractionTrackingP
113
147
  x: event.clientX,
114
148
  y: event.clientY
115
149
  };
116
- // Use document-level mousemove so we get events when hovering over block
117
- // controls (which may be in portals outside the editor DOM). Without this,
118
- // handleDOMEvents.mousemove only fires when over the editor content.
119
- if (editorContentArea.contains(event.target) || isMovingToBlockControlsArea(event.target)) {
120
- handleMouseMove(view, event, rightSideControlsEnabled);
150
+ // Catches block controls in portals that handleDOMEvents.mousemove misses.
151
+ // The right-margin overlay is only relevant with the experiment on.
152
+ var overClickArea = marginHoverEnabled && event.target instanceof Element && !!event.target.closest(CLICK_AREA_SELECTOR);
153
+ if (editorContentArea.contains(event.target) || isMovingToBlockControlsArea(event.target) || overClickArea) {
154
+ handleMouseMove(view, event, rightSideControlsEnabled, api);
121
155
  }
122
156
  },
123
157
  options: {
@@ -66,6 +66,8 @@ export var VisibilityContainer = function VisibilityContainer(_ref) {
66
66
  rightSideControlsEnabled = _useSharedPluginState.rightSideControlsEnabled;
67
67
  var isViewMode = editorViewMode === 'view';
68
68
  // rightSideControlsEnabled is the single source of truth (confluence_remix_button_right_side_block_fg from preset)
69
+ // Both controls are restricted by side: the drag handle shows on the left half, the Remix button on
70
+ // the right half (and the right margin), matching the midpoint split in handle-mouse-move.
69
71
  var shouldRestrictBySide = rightSideControlsEnabled && controlSide !== undefined && !isViewMode;
70
72
  // Only restrict by side when hoverSide is known (after mousemove). When undefined, show both
71
73
  // controls so drag handle is visible on load and for keyboard-only users.
@@ -0,0 +1,2 @@
1
+ /** Far-right margin width reserved for the Rovo button, where the Remix button must not show. */
2
+ export declare const RIGHT_MARGIN_ROVO_GAP_PX = 80;
@@ -1,4 +1,6 @@
1
+ import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
1
2
  import type { EditorView } from '@atlaskit/editor-prosemirror/view';
2
- export declare const handleMouseMove: (view: EditorView, event: Event, rightSideControlsEnabled?: boolean) => boolean;
3
+ import type { BlockControlsPlugin } from '../../blockControlsPluginType';
4
+ export declare const handleMouseMove: (view: EditorView, event: Event, rightSideControlsEnabled?: boolean, api?: ExtractInjectionAPI<BlockControlsPlugin>) => boolean;
3
5
  export declare const handleMouseLeave: (view: EditorView, rightSideControlsEnabled?: boolean) => boolean;
4
6
  export declare const handleMouseEnter: (view: EditorView) => boolean;
@@ -1,6 +1,8 @@
1
1
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
2
3
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
3
4
  import type { EditorState } from '@atlaskit/editor-prosemirror/state';
5
+ import type { BlockControlsPlugin } from '../../blockControlsPluginType';
4
6
  export type InteractionTrackingPluginState = {
5
7
  /**
6
8
  * Tracks which side of the editor the mouse is currently on.
@@ -16,5 +18,5 @@ export type InteractionTrackingPluginState = {
16
18
  isMouseOut?: boolean;
17
19
  };
18
20
  export declare const interactionTrackingPluginKey: PluginKey<InteractionTrackingPluginState>;
19
- export declare const createInteractionTrackingPlugin: (rightSideControlsEnabled?: boolean) => SafePlugin<InteractionTrackingPluginState>;
21
+ export declare const createInteractionTrackingPlugin: (rightSideControlsEnabled?: boolean, api?: ExtractInjectionAPI<BlockControlsPlugin>) => SafePlugin<InteractionTrackingPluginState>;
20
22
  export declare const getInteractionTrackingState: (state: EditorState) => InteractionTrackingPluginState | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-block-controls",
3
- "version": "12.4.1",
3
+ "version": "13.0.0",
4
4
  "description": "Block controls plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -15,50 +15,42 @@
15
15
  "module": "dist/esm/index.js",
16
16
  "module:es2019": "dist/es2019/index.js",
17
17
  "types": "dist/types/index.d.ts",
18
- "typesVersions": {
19
- ">=4.5 <4.9": {
20
- "*": [
21
- "dist/types-ts4.5/*",
22
- "dist/types-ts4.5/index.d.ts"
23
- ]
24
- }
25
- },
26
18
  "sideEffects": [
27
19
  "*.compiled.css"
28
20
  ],
29
21
  "atlaskit:src": "src/index.ts",
30
22
  "dependencies": {
31
- "@atlaskit/browser-apis": "^0.0.2",
32
- "@atlaskit/button": "^23.11.0",
33
- "@atlaskit/editor-plugin-accessibility-utils": "^11.0.0",
34
- "@atlaskit/editor-plugin-analytics": "^11.0.0",
35
- "@atlaskit/editor-plugin-editor-disabled": "^11.0.0",
36
- "@atlaskit/editor-plugin-editor-viewmode": "^13.0.0",
37
- "@atlaskit/editor-plugin-feature-flags": "^10.0.0",
38
- "@atlaskit/editor-plugin-interaction": "^20.0.0",
39
- "@atlaskit/editor-plugin-limited-mode": "^8.0.0",
40
- "@atlaskit/editor-plugin-metrics": "^12.0.0",
41
- "@atlaskit/editor-plugin-quick-insert": "^11.1.0",
42
- "@atlaskit/editor-plugin-selection": "^11.0.0",
43
- "@atlaskit/editor-plugin-toolbar": "^8.0.0",
44
- "@atlaskit/editor-plugin-type-ahead": "^11.3.0",
45
- "@atlaskit/editor-plugin-user-intent": "^9.1.0",
46
- "@atlaskit/editor-plugin-width": "^12.0.0",
47
- "@atlaskit/editor-prosemirror": "^7.3.0",
48
- "@atlaskit/editor-shared-styles": "^3.11.0",
49
- "@atlaskit/editor-tables": "^2.10.0",
50
- "@atlaskit/icon": "^35.4.0",
51
- "@atlaskit/icon-lab": "^6.14.0",
52
- "@atlaskit/link": "^3.4.0",
53
- "@atlaskit/platform-feature-flags": "^1.1.0",
54
- "@atlaskit/pragmatic-drag-and-drop": "^1.8.0",
55
- "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.0",
56
- "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^3.2.0",
57
- "@atlaskit/primitives": "^19.0.0",
58
- "@atlaskit/theme": "^25.0.0",
59
- "@atlaskit/tmp-editor-statsig": "^102.0.0",
60
- "@atlaskit/tokens": "^13.4.0",
61
- "@atlaskit/tooltip": "^22.6.0",
23
+ "@atlaskit/browser-apis": "^1.0.0",
24
+ "@atlaskit/button": "^24.0.0",
25
+ "@atlaskit/editor-plugin-accessibility-utils": "^12.0.0",
26
+ "@atlaskit/editor-plugin-analytics": "^12.0.0",
27
+ "@atlaskit/editor-plugin-editor-disabled": "^12.0.0",
28
+ "@atlaskit/editor-plugin-editor-viewmode": "^14.0.0",
29
+ "@atlaskit/editor-plugin-feature-flags": "^11.0.0",
30
+ "@atlaskit/editor-plugin-interaction": "^21.0.0",
31
+ "@atlaskit/editor-plugin-limited-mode": "^9.0.0",
32
+ "@atlaskit/editor-plugin-metrics": "^13.0.0",
33
+ "@atlaskit/editor-plugin-quick-insert": "^12.0.0",
34
+ "@atlaskit/editor-plugin-selection": "^12.0.0",
35
+ "@atlaskit/editor-plugin-toolbar": "^9.0.0",
36
+ "@atlaskit/editor-plugin-type-ahead": "^12.0.0",
37
+ "@atlaskit/editor-plugin-user-intent": "^10.0.0",
38
+ "@atlaskit/editor-plugin-width": "^13.0.0",
39
+ "@atlaskit/editor-prosemirror": "^8.0.0",
40
+ "@atlaskit/editor-shared-styles": "^4.0.0",
41
+ "@atlaskit/editor-tables": "^3.0.0",
42
+ "@atlaskit/icon": "^36.0.0",
43
+ "@atlaskit/icon-lab": "^7.0.0",
44
+ "@atlaskit/link": "^4.0.0",
45
+ "@atlaskit/platform-feature-flags": "^2.0.0",
46
+ "@atlaskit/pragmatic-drag-and-drop": "^2.0.0",
47
+ "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^3.0.0",
48
+ "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^4.0.0",
49
+ "@atlaskit/primitives": "^20.0.0",
50
+ "@atlaskit/theme": "^26.0.0",
51
+ "@atlaskit/tmp-editor-statsig": "^104.0.0",
52
+ "@atlaskit/tokens": "^14.0.0",
53
+ "@atlaskit/tooltip": "^23.0.0",
62
54
  "@babel/runtime": "^7.0.0",
63
55
  "@emotion/react": "^11.7.1",
64
56
  "bind-event-listener": "^3.0.0",
@@ -67,7 +59,7 @@
67
59
  "uuid": "^3.1.0"
68
60
  },
69
61
  "peerDependencies": {
70
- "@atlaskit/editor-common": "^115.15.0",
62
+ "@atlaskit/editor-common": "^116.0.0",
71
63
  "react": "^18.2.0",
72
64
  "react-dom": "^18.2.0",
73
65
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
@@ -149,6 +141,10 @@
149
141
  },
150
142
  "confluence_frontend_native_tabs_extension": {
151
143
  "type": "boolean"
144
+ },
145
+ "remix_button_right_margin_hover": {
146
+ "type": "boolean",
147
+ "referenceOnly": true
152
148
  }
153
149
  },
154
150
  "devDependencies": {
@@ -1,2 +0,0 @@
1
- import type { BlockControlsPlugin } from './blockControlsPluginType';
2
- export declare const blockControlsPlugin: BlockControlsPlugin;