@atlaskit/editor-plugin-block-controls 8.7.2 → 8.8.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 (55) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/cjs/blockControlsPlugin.js +14 -3
  3. package/dist/cjs/pm-plugins/decorations-drag-handle.js +3 -0
  4. package/dist/cjs/pm-plugins/decorations-quick-insert-button.js +3 -0
  5. package/dist/cjs/pm-plugins/handle-mouse-over.js +27 -11
  6. package/dist/cjs/pm-plugins/interaction-tracking/commands.js +12 -1
  7. package/dist/cjs/pm-plugins/interaction-tracking/handle-mouse-move.js +96 -1
  8. package/dist/cjs/pm-plugins/interaction-tracking/pm-plugin.js +92 -3
  9. package/dist/cjs/pm-plugins/main.js +129 -25
  10. package/dist/cjs/pm-plugins/vanilla-quick-insert.js +36 -13
  11. package/dist/cjs/ui/drag-handle.js +19 -9
  12. package/dist/cjs/ui/global-styles.js +9 -4
  13. package/dist/cjs/ui/quick-insert-button.js +16 -3
  14. package/dist/cjs/ui/visibility-container.js +70 -9
  15. package/dist/es2019/blockControlsPlugin.js +12 -3
  16. package/dist/es2019/pm-plugins/decorations-drag-handle.js +3 -0
  17. package/dist/es2019/pm-plugins/decorations-quick-insert-button.js +3 -0
  18. package/dist/es2019/pm-plugins/handle-mouse-over.js +27 -11
  19. package/dist/es2019/pm-plugins/interaction-tracking/commands.js +11 -0
  20. package/dist/es2019/pm-plugins/interaction-tracking/handle-mouse-move.js +98 -3
  21. package/dist/es2019/pm-plugins/interaction-tracking/pm-plugin.js +89 -4
  22. package/dist/es2019/pm-plugins/main.js +73 -18
  23. package/dist/es2019/pm-plugins/vanilla-quick-insert.js +27 -3
  24. package/dist/es2019/ui/drag-handle.js +19 -9
  25. package/dist/es2019/ui/global-styles.js +9 -3
  26. package/dist/es2019/ui/quick-insert-button.js +17 -3
  27. package/dist/es2019/ui/visibility-container.js +65 -9
  28. package/dist/esm/blockControlsPlugin.js +14 -3
  29. package/dist/esm/pm-plugins/decorations-drag-handle.js +3 -0
  30. package/dist/esm/pm-plugins/decorations-quick-insert-button.js +3 -0
  31. package/dist/esm/pm-plugins/handle-mouse-over.js +27 -11
  32. package/dist/esm/pm-plugins/interaction-tracking/commands.js +11 -0
  33. package/dist/esm/pm-plugins/interaction-tracking/handle-mouse-move.js +98 -2
  34. package/dist/esm/pm-plugins/interaction-tracking/pm-plugin.js +93 -3
  35. package/dist/esm/pm-plugins/main.js +129 -25
  36. package/dist/esm/pm-plugins/vanilla-quick-insert.js +36 -13
  37. package/dist/esm/ui/drag-handle.js +19 -9
  38. package/dist/esm/ui/global-styles.js +9 -4
  39. package/dist/esm/ui/quick-insert-button.js +16 -3
  40. package/dist/esm/ui/visibility-container.js +68 -9
  41. package/dist/types/blockControlsPluginType.d.ts +25 -1
  42. package/dist/types/index.d.ts +1 -1
  43. package/dist/types/pm-plugins/interaction-tracking/commands.d.ts +2 -0
  44. package/dist/types/pm-plugins/interaction-tracking/handle-mouse-move.d.ts +2 -2
  45. package/dist/types/pm-plugins/interaction-tracking/pm-plugin.d.ts +5 -1
  46. package/dist/types/pm-plugins/main.d.ts +2 -2
  47. package/dist/types/ui/visibility-container.d.ts +2 -1
  48. package/dist/types-ts4.5/blockControlsPluginType.d.ts +27 -1
  49. package/dist/types-ts4.5/index.d.ts +1 -1
  50. package/dist/types-ts4.5/pm-plugins/interaction-tracking/commands.d.ts +2 -0
  51. package/dist/types-ts4.5/pm-plugins/interaction-tracking/handle-mouse-move.d.ts +2 -2
  52. package/dist/types-ts4.5/pm-plugins/interaction-tracking/pm-plugin.d.ts +5 -1
  53. package/dist/types-ts4.5/pm-plugins/main.d.ts +2 -2
  54. package/dist/types-ts4.5/ui/visibility-container.d.ts +2 -1
  55. package/package.json +5 -7
@@ -3,6 +3,7 @@ import { expandSelectionBounds } from '@atlaskit/editor-common/selection';
3
3
  import { areToolbarFlagsEnabled } from '@atlaskit/editor-common/toolbar-flag-check';
4
4
  import { TextSelection } from '@atlaskit/editor-prosemirror/state';
5
5
  import { fg } from '@atlaskit/platform-feature-flags';
6
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
6
7
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
7
8
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
8
9
  import { handleKeyDownWithPreservedSelection } from './editor-commands/handle-key-down-with-preserved-selection';
@@ -20,9 +21,12 @@ import { createSelectionPreservationPlugin } from './pm-plugins/selection-preser
20
21
  import { selectNode } from './pm-plugins/utils/getSelection';
21
22
  import { GlobalStylesWrapper } from './ui/global-styles';
22
23
  export const blockControlsPlugin = ({
23
- api
24
+ api,
25
+ config
24
26
  }) => {
27
+ var _config$rightSideCont;
25
28
  const nodeDecorationRegistry = [];
29
+ const rightSideControlsEnabled = (_config$rightSideCont = config === null || config === void 0 ? void 0 : config.rightSideControlsEnabled) !== null && _config$rightSideCont !== void 0 ? _config$rightSideCont : false;
26
30
  return {
27
31
  name: 'blockControls',
28
32
  actions: {
@@ -42,12 +46,12 @@ export const blockControlsPlugin = ({
42
46
  plugin: ({
43
47
  getIntl,
44
48
  nodeViewPortalProviderAPI
45
- }) => createPlugin(api, getIntl, nodeViewPortalProviderAPI, nodeDecorationRegistry)
49
+ }) => createPlugin(api, getIntl, nodeViewPortalProviderAPI, nodeDecorationRegistry, rightSideControlsEnabled && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true))
46
50
  }];
47
51
  if (editorExperiment('platform_editor_controls', 'variant1')) {
48
52
  pmPlugins.push({
49
53
  name: 'blockControlsInteractionTrackingPlugin',
50
- plugin: createInteractionTrackingPlugin
54
+ plugin: () => createInteractionTrackingPlugin(rightSideControlsEnabled && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true))
51
55
  });
52
56
  }
53
57
  if (expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
@@ -317,6 +321,11 @@ export const blockControlsPlugin = ({
317
321
  if (editorExperiment('platform_editor_controls', 'variant1')) {
318
322
  var _interactionTrackingP2, _interactionTrackingP3;
319
323
  sharedState.isMouseOut = (_interactionTrackingP2 = (_interactionTrackingP3 = interactionTrackingPluginKey.getState(editorState)) === null || _interactionTrackingP3 === void 0 ? void 0 : _interactionTrackingP3.isMouseOut) !== null && _interactionTrackingP2 !== void 0 ? _interactionTrackingP2 : false;
324
+ sharedState.rightSideControlsEnabled = rightSideControlsEnabled && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true);
325
+ if (rightSideControlsEnabled && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true)) {
326
+ var _interactionTrackingP4;
327
+ sharedState.hoverSide = (_interactionTrackingP4 = interactionTrackingPluginKey.getState(editorState)) === null || _interactionTrackingP4 === void 0 ? void 0 : _interactionTrackingP4.hoverSide;
328
+ }
320
329
  }
321
330
  if (expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
322
331
  var _selectionPreservatio;
@@ -76,6 +76,9 @@ export const dragHandleDecoration = ({
76
76
  const element = document.createElement('span');
77
77
  // inline decoration causes focus issues when refocusing Editor into first line
78
78
  element.style.display = 'block';
79
+ if (expValEquals('confluence_remix_icon_right_side', 'isEnabled', true)) {
80
+ element.setAttribute('data-blocks-decorator-widget', 'true');
81
+ }
79
82
  element.setAttribute('data-testid', 'block-ctrl-decorator-widget');
80
83
  element.setAttribute('data-blocks-drag-handle-container', 'true');
81
84
  element.setAttribute('data-blocks-drag-handle-key', key);
@@ -67,6 +67,9 @@ export const quickInsertButtonDecoration = ({
67
67
  }
68
68
  element.contentEditable = 'false';
69
69
  element.setAttribute('data-blocks-quick-insert-container', 'true');
70
+ if (expValEquals('confluence_remix_icon_right_side', 'isEnabled', true)) {
71
+ element.setAttribute('data-blocks-quick-insert-button', 'true');
72
+ }
70
73
  element.setAttribute('data-testid', 'block-ctrl-quick-insert-button');
71
74
  if (editorExperiment('platform_editor_block_control_optimise_render', true, {
72
75
  exposure: true
@@ -35,7 +35,7 @@ const getDefaultNodeSelector = memoizeOne(() => {
35
35
  return getNodeSelector([...IGNORE_NODES_NEXT, 'media'], [...IGNORE_NODE_DESCENDANTS_ADVANCED_LAYOUT, 'table']);
36
36
  });
37
37
  export const handleMouseOver = (view, event, api) => {
38
- var _api$blockControls, _api$editorDisabled, _target$classList;
38
+ var _api$blockControls, _api$editorDisabled, _api$editorViewMode, _api$editorViewMode$s, _api$blockControls$sh, _api$blockControls2, _api$blockControls2$s, _target$classList;
39
39
  const {
40
40
  isDragging,
41
41
  activeNode,
@@ -48,10 +48,14 @@ export const handleMouseOver = (view, event, api) => {
48
48
  } = (api === null || api === void 0 ? void 0 : (_api$editorDisabled = api.editorDisabled) === null || _api$editorDisabled === void 0 ? void 0 : _api$editorDisabled.sharedState.currentState()) || {
49
49
  editorDisabled: false
50
50
  };
51
+ const editorViewMode = api === null || api === void 0 ? void 0 : (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 ? void 0 : (_api$editorViewMode$s = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode$s === void 0 ? void 0 : _api$editorViewMode$s.mode;
52
+ const isViewMode = editorViewMode === 'view';
51
53
  const toolbarFlagsEnabled = areToolbarFlagsEnabled(Boolean(api === null || api === void 0 ? void 0 : api.toolbar));
52
54
 
53
- // We shouldn't be firing mouse over transactions when the editor is disabled
54
- if (editorDisabled) {
55
+ // We shouldn't be firing mouse over transactions when the editor is disabled,
56
+ // except in view mode when right-side controls are enabled (show controls on block hover)
57
+ const rightSideControlsEnabled = (_api$blockControls$sh = api === null || api === void 0 ? void 0 : (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 ? void 0 : (_api$blockControls2$s = _api$blockControls2.sharedState.currentState()) === null || _api$blockControls2$s === void 0 ? void 0 : _api$blockControls2$s.rightSideControlsEnabled) !== null && _api$blockControls$sh !== void 0 ? _api$blockControls$sh : false;
58
+ if (editorDisabled && (!isViewMode || !(rightSideControlsEnabled && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true)))) {
55
59
  return false;
56
60
  }
57
61
 
@@ -74,6 +78,18 @@ export const handleMouseOver = (view, event, api) => {
74
78
  return false;
75
79
  }
76
80
  let rootElement = target === null || target === void 0 ? void 0 : target.closest(isNativeAnchorSupported ? getDefaultNodeSelector() : `[data-drag-handler-anchor-name]`);
81
+
82
+ // When hovering over the right-edge button (rendered in a portal outside the block), resolve the
83
+ // block from the container's anchor so activeNode stays set and the button remains visible.
84
+ if (!rootElement && rightSideControlsEnabled && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true)) {
85
+ const rightEdgeContainer = target === null || target === void 0 ? void 0 : target.closest('[data-blocks-right-edge-button-container]');
86
+ if (rightEdgeContainer) {
87
+ const anchor = rightEdgeContainer.getAttribute('data-blocks-right-edge-button-anchor');
88
+ if (anchor) {
89
+ rootElement = view.dom.querySelector(`[${getAnchorAttrName()}="${CSS.escape(anchor)}"]`);
90
+ }
91
+ }
92
+ }
77
93
  if (rootElement) {
78
94
  var _rootElement$parentEl;
79
95
  // We want to exlude handles from showing for empty paragraph and heading nodes
@@ -182,22 +198,22 @@ export const handleMouseOver = (view, event, api) => {
182
198
  // as when it is a multi-selection, the showDragHandleAt command interfere with selection
183
199
  // sometimes makes the multi-selection not continous after block menu is opened with keyboard
184
200
  if (!(isMultipleSelected && isMenuOpen && blockMenuOptions !== null && blockMenuOptions !== void 0 && blockMenuOptions.openedViaKeyboard)) {
185
- var _api$core, _api$blockControls2, _rootPos, _rootAnchorName, _rootNodeType;
186
- api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 ? void 0 : _api$blockControls2.commands.showDragHandleAt(targetPos, anchorName, nodeType, undefined, (_rootPos = rootPos) !== null && _rootPos !== void 0 ? _rootPos : targetPos, (_rootAnchorName = rootAnchorName) !== null && _rootAnchorName !== void 0 ? _rootAnchorName : anchorName, (_rootNodeType = rootNodeType) !== null && _rootNodeType !== void 0 ? _rootNodeType : nodeType));
201
+ var _api$core, _api$blockControls3, _rootPos, _rootAnchorName, _rootNodeType;
202
+ api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 ? void 0 : _api$blockControls3.commands.showDragHandleAt(targetPos, anchorName, nodeType, undefined, (_rootPos = rootPos) !== null && _rootPos !== void 0 ? _rootPos : targetPos, (_rootAnchorName = rootAnchorName) !== null && _rootAnchorName !== void 0 ? _rootAnchorName : anchorName, (_rootNodeType = rootNodeType) !== null && _rootNodeType !== void 0 ? _rootNodeType : nodeType));
187
203
  }
188
204
  } else {
189
- var _api$core2, _api$blockControls3, _rootPos2, _rootAnchorName2, _rootNodeType2;
190
- api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 ? void 0 : _api$blockControls3.commands.showDragHandleAt(targetPos, anchorName, nodeType, undefined, (_rootPos2 = rootPos) !== null && _rootPos2 !== void 0 ? _rootPos2 : targetPos, (_rootAnchorName2 = rootAnchorName) !== null && _rootAnchorName2 !== void 0 ? _rootAnchorName2 : anchorName, (_rootNodeType2 = rootNodeType) !== null && _rootNodeType2 !== void 0 ? _rootNodeType2 : nodeType));
205
+ var _api$core2, _api$blockControls4, _rootPos2, _rootAnchorName2, _rootNodeType2;
206
+ api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 ? void 0 : _api$blockControls4.commands.showDragHandleAt(targetPos, anchorName, nodeType, undefined, (_rootPos2 = rootPos) !== null && _rootPos2 !== void 0 ? _rootPos2 : targetPos, (_rootAnchorName2 = rootAnchorName) !== null && _rootAnchorName2 !== void 0 ? _rootAnchorName2 : anchorName, (_rootNodeType2 = rootNodeType) !== null && _rootNodeType2 !== void 0 ? _rootNodeType2 : nodeType));
191
207
  }
192
208
  } else {
193
- var _api$core3, _api$blockControls4;
194
- api === null || api === void 0 ? void 0 : (_api$core3 = api.core) === null || _api$core3 === void 0 ? void 0 : _api$core3.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 ? void 0 : _api$blockControls4.commands.showDragHandleAt(targetPos, anchorName, nodeType));
209
+ var _api$core3, _api$blockControls5;
210
+ api === null || api === void 0 ? void 0 : (_api$core3 = api.core) === null || _api$core3 === void 0 ? void 0 : _api$core3.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls5 = api.blockControls) === null || _api$blockControls5 === void 0 ? void 0 : _api$blockControls5.commands.showDragHandleAt(targetPos, anchorName, nodeType));
195
211
  }
196
212
  if (expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
197
213
  var _api$userIntent, _api$userIntent$share;
198
214
  if (isMenuOpen && originalAnchorName && (api === null || api === void 0 ? void 0 : (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 ? void 0 : (_api$userIntent$share = _api$userIntent.sharedState.currentState()) === null || _api$userIntent$share === void 0 ? void 0 : _api$userIntent$share.currentUserIntent) === 'blockMenuOpen') {
199
- var _api$core4, _api$blockControls5;
200
- api === null || api === void 0 ? void 0 : (_api$core4 = api.core) === null || _api$core4 === void 0 ? void 0 : _api$core4.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls5 = api.blockControls) === null || _api$blockControls5 === void 0 ? void 0 : _api$blockControls5.commands.toggleBlockMenu());
215
+ var _api$core4, _api$blockControls6;
216
+ api === null || api === void 0 ? void 0 : (_api$core4 = api.core) === null || _api$core4 === void 0 ? void 0 : _api$core4.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls6 = api.blockControls) === null || _api$blockControls6 === void 0 ? void 0 : _api$blockControls6.commands.toggleBlockMenu());
201
217
  }
202
218
  }
203
219
  }
@@ -18,4 +18,15 @@ export const mouseEnter = view => {
18
18
  view.dispatch(view.state.tr.setMeta(interactionTrackingPluginKey, {
19
19
  type: 'mouseEnter'
20
20
  }));
21
+ };
22
+ export const setHoverSide = (view, side) => {
23
+ view.dispatch(view.state.tr.setMeta(interactionTrackingPluginKey, {
24
+ type: 'setHoverSide',
25
+ side
26
+ }));
27
+ };
28
+ export const clearHoverSide = view => {
29
+ view.dispatch(view.state.tr.setMeta(interactionTrackingPluginKey, {
30
+ type: 'clearHoverSide'
31
+ }));
21
32
  };
@@ -1,14 +1,109 @@
1
- import { mouseEnter, mouseLeave, stopEditing } from './commands';
1
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
2
+ import { clearHoverSide, mouseEnter, mouseLeave, setHoverSide, stopEditing } from './commands';
2
3
  import { getInteractionTrackingState } from './pm-plugin';
3
- export const handleMouseMove = view => {
4
+
5
+ /** Per-view pending hover state; avoids cross-editor singleton. */
6
+ const pendingByView = new WeakMap();
7
+
8
+ /** Per-view RAF handle so clearPendingHoverSide cancels only that view's callback. */
9
+ const rafIdByView = new WeakMap();
10
+ const cancelScheduledProcessForView = view => {
11
+ const id = rafIdByView.get(view);
12
+ if (id !== undefined) {
13
+ cancelAnimationFrame(id);
14
+ rafIdByView.delete(view);
15
+ }
16
+ };
17
+ const clearPendingHoverSide = view => {
18
+ pendingByView.delete(view);
19
+ cancelScheduledProcessForView(view);
20
+ };
21
+ const BLOCK_SELECTORS = '[data-node-anchor], [data-drag-handler-anchor-name]';
22
+ const TABLE_SELECTOR = '[data-prosemirror-node-name="table"]';
23
+ const RIGHT_EDGE_SELECTOR = '[data-blocks-right-edge-button-container]';
24
+ const isInsideTable = element => {
25
+ var _element$getAttribute;
26
+ return element.closest(TABLE_SELECTOR) !== null || ((_element$getAttribute = element.getAttribute) === null || _element$getAttribute === void 0 ? void 0 : _element$getAttribute.call(element, 'data-prosemirror-node-name')) === 'table';
27
+ };
28
+ const processHoverSide = view => {
29
+ const event = pendingByView.get(view);
30
+ if (!event) {
31
+ return;
32
+ }
33
+ pendingByView.delete(view);
34
+ rafIdByView.delete(view);
35
+ const editorContentArea = view.dom.closest('.ak-editor-content-area');
36
+ if (!(editorContentArea instanceof HTMLElement)) {
37
+ return;
38
+ }
39
+ const state = getInteractionTrackingState(view.state);
40
+ const target = event.target instanceof HTMLElement ? event.target : null;
41
+
42
+ // When hovering over block controls directly, infer side from which control we're over.
43
+ // This is more reliable than bounds when controls are in portals outside the editor DOM.
44
+ const rightEdgeElement = target === null || target === void 0 ? void 0 : target.closest(RIGHT_EDGE_SELECTOR);
45
+ if (rightEdgeElement) {
46
+ if ((state === null || state === void 0 ? void 0 : state.hoverSide) !== 'right') {
47
+ setHoverSide(view, 'right');
48
+ }
49
+ return;
50
+ }
51
+ const leftControlElement = target === null || target === void 0 ? void 0 : target.closest('[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"]');
52
+ if (leftControlElement) {
53
+ if ((state === null || state === void 0 ? void 0 : state.hoverSide) !== 'left') {
54
+ setHoverSide(view, 'left');
55
+ }
56
+ return;
57
+ }
58
+
59
+ // Use the hovered block's midpoint when hovering over block content.
60
+ const blockElement = target === null || target === void 0 ? void 0 : target.closest(BLOCK_SELECTORS);
61
+ const boundsElement = blockElement instanceof HTMLElement ? blockElement : editorContentArea;
62
+
63
+ // Tables show block controls at table level; don't restrict by side so drag handle
64
+ // stays visible when hovering anywhere in the table (e.g. paragraph in second cell).
65
+ if (isInsideTable(boundsElement)) {
66
+ if ((state === null || state === void 0 ? void 0 : state.hoverSide) !== undefined) {
67
+ clearHoverSide(view);
68
+ }
69
+ return;
70
+ }
71
+ const {
72
+ left,
73
+ right
74
+ } = boundsElement.getBoundingClientRect();
75
+ const midpoint = (left + right) / 2;
76
+ const nextHoverSide = event.clientX > midpoint ? 'right' : 'left';
77
+ if ((state === null || state === void 0 ? void 0 : state.hoverSide) !== nextHoverSide) {
78
+ setHoverSide(view, nextHoverSide);
79
+ }
80
+ };
81
+ export const handleMouseMove = (view, event, rightSideControlsEnabled = false) => {
4
82
  const state = getInteractionTrackingState(view.state);
5
83
  // if user has stopped editing and moved their mouse, show block controls again
6
84
  if (state !== null && state !== void 0 && state.isEditing) {
7
85
  stopEditing(view);
8
86
  }
87
+
88
+ // Only track hover side when right-side controls are enabled and experiment is on (performance)
89
+ if (!rightSideControlsEnabled || !expValEquals('confluence_remix_icon_right_side', 'isEnabled', true)) {
90
+ return false;
91
+ }
92
+ if (!(event instanceof MouseEvent)) {
93
+ return false;
94
+ }
95
+ pendingByView.set(view, event);
96
+ cancelScheduledProcessForView(view);
97
+ const id = requestAnimationFrame(() => {
98
+ processHoverSide(view);
99
+ });
100
+ rafIdByView.set(view, id);
9
101
  return false;
10
102
  };
11
- export const handleMouseLeave = view => {
103
+ export const handleMouseLeave = (view, rightSideControlsEnabled = false) => {
104
+ if (rightSideControlsEnabled && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true)) {
105
+ clearPendingHoverSide(view);
106
+ }
12
107
  mouseLeave(view);
13
108
  return false;
14
109
  };
@@ -1,11 +1,17 @@
1
1
  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
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
4
5
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
5
6
  import { handleKeyDown } from './handle-key-down';
6
7
  import { handleMouseEnter, handleMouseLeave, handleMouseMove } from './handle-mouse-move';
8
+
9
+ /** Elements that extend the editor hover area (block controls, right-edge button, etc.) */
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"]';
11
+ const MOUSE_LEAVE_DEBOUNCE_MS = 200;
12
+ const isMovingToBlockControlsArea = target => target instanceof Element && !!target.closest(BLOCK_CONTROLS_HOVER_AREA_SELECTOR);
7
13
  export const interactionTrackingPluginKey = new PluginKey('interactionTrackingPlugin');
8
- export const createInteractionTrackingPlugin = () => {
14
+ export const createInteractionTrackingPlugin = (rightSideControlsEnabled = false) => {
9
15
  return new SafePlugin({
10
16
  key: interactionTrackingPluginKey,
11
17
  state: {
@@ -30,10 +36,17 @@ export const createInteractionTrackingPlugin = () => {
30
36
  break;
31
37
  case 'mouseLeave':
32
38
  newState.isMouseOut = true;
39
+ newState.hoverSide = undefined;
33
40
  break;
34
41
  case 'mouseEnter':
35
42
  newState.isMouseOut = false;
36
43
  break;
44
+ case 'setHoverSide':
45
+ newState.hoverSide = meta.side;
46
+ break;
47
+ case 'clearHoverSide':
48
+ newState.hoverSide = undefined;
49
+ break;
37
50
  }
38
51
  return {
39
52
  ...pluginState,
@@ -44,30 +57,102 @@ export const createInteractionTrackingPlugin = () => {
44
57
  props: {
45
58
  handleKeyDown,
46
59
  handleDOMEvents: {
47
- mousemove: handleMouseMove
60
+ mousemove: (view, event) => handleMouseMove(view, event, rightSideControlsEnabled && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true))
48
61
  }
49
62
  },
50
63
  view: editorExperiment('platform_editor_controls', 'variant1') ? view => {
51
64
  const editorContentArea = view.dom.closest('.ak-editor-content-area');
65
+ const remixRightSideEnabled = rightSideControlsEnabled && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true);
52
66
  let unbindMouseEnter;
53
67
  let unbindMouseLeave;
68
+ let unbindDocumentMouseMove;
69
+ let mouseLeaveTimeoutId = null;
70
+ let lastMousePosition = {
71
+ x: 0,
72
+ y: 0
73
+ };
74
+ const scheduleMouseLeave = event => {
75
+ if (mouseLeaveTimeoutId) {
76
+ clearTimeout(mouseLeaveTimeoutId);
77
+ mouseLeaveTimeoutId = null;
78
+ }
79
+
80
+ // Don't set isMouseOut when moving to block controls (right-edge button, drag handle, etc.)
81
+ if (rightSideControlsEnabled && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true) && isMovingToBlockControlsArea(event.relatedTarget)) {
82
+ return;
83
+ }
84
+ mouseLeaveTimeoutId = setTimeout(() => {
85
+ mouseLeaveTimeoutId = null;
86
+ // Before dispatching, check if mouse has moved to block controls (e.g. through empty space)
87
+ if (rightSideControlsEnabled && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true) && typeof document !== 'undefined') {
88
+ const el = document.elementFromPoint(lastMousePosition.x, lastMousePosition.y);
89
+ if (el && isMovingToBlockControlsArea(el)) {
90
+ return;
91
+ }
92
+ }
93
+ handleMouseLeave(view, rightSideControlsEnabled && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true));
94
+ }, MOUSE_LEAVE_DEBOUNCE_MS);
95
+ };
96
+ const cancelScheduledMouseLeave = () => {
97
+ if (mouseLeaveTimeoutId) {
98
+ clearTimeout(mouseLeaveTimeoutId);
99
+ mouseLeaveTimeoutId = null;
100
+ }
101
+ };
54
102
  if (editorContentArea) {
103
+ if (remixRightSideEnabled && typeof document !== 'undefined') {
104
+ unbindDocumentMouseMove = bind(document, {
105
+ type: 'mousemove',
106
+ listener: event => {
107
+ lastMousePosition = {
108
+ x: event.clientX,
109
+ y: event.clientY
110
+ };
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 && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true));
116
+ }
117
+ },
118
+ options: {
119
+ passive: true
120
+ }
121
+ });
122
+ }
55
123
  unbindMouseEnter = bind(editorContentArea, {
56
124
  type: 'mouseenter',
57
125
  listener: () => {
126
+ if (remixRightSideEnabled) {
127
+ cancelScheduledMouseLeave();
128
+ }
58
129
  handleMouseEnter(view);
59
130
  }
60
131
  });
61
132
  unbindMouseLeave = bind(editorContentArea, {
62
133
  type: 'mouseleave',
63
- listener: () => {
64
- handleMouseLeave(view);
134
+ listener: event => {
135
+ const e = event;
136
+ lastMousePosition = {
137
+ x: e.clientX,
138
+ y: e.clientY
139
+ };
140
+ if (remixRightSideEnabled) {
141
+ scheduleMouseLeave(e);
142
+ } else {
143
+ handleMouseLeave(view, false);
144
+ }
65
145
  }
66
146
  });
67
147
  }
68
148
  return {
69
149
  destroy: () => {
70
150
  var _unbindMouseEnter, _unbindMouseLeave;
151
+ if (remixRightSideEnabled) {
152
+ var _unbindDocumentMouseM;
153
+ cancelScheduledMouseLeave();
154
+ (_unbindDocumentMouseM = unbindDocumentMouseMove) === null || _unbindDocumentMouseM === void 0 ? void 0 : _unbindDocumentMouseM();
155
+ }
71
156
  (_unbindMouseEnter = unbindMouseEnter) === null || _unbindMouseEnter === void 0 ? void 0 : _unbindMouseEnter();
72
157
  (_unbindMouseLeave = unbindMouseLeave) === null || _unbindMouseLeave === void 0 ? void 0 : _unbindMouseLeave();
73
158
  }
@@ -234,8 +234,8 @@ const getDecorationAtPos = (state, decorations, pos, to) => {
234
234
  const nodeDecAtActivePos = nodeDecsAtActivePos.pop();
235
235
  return nodeDecAtActivePos;
236
236
  };
237
- export const apply = (api, formatMessage, tr, currentState, newState, flags, nodeViewPortalProviderAPI, nodeDecorationRegistry, anchorRectCache, resizeObserverWidth, pragmaticCleanup) => {
238
- var _api$limitedMode, _api$limitedMode$shar, _api$limitedMode$shar2, _meta$multiSelectDnD, _activeNode, _activeNode2, _meta$activeNode$hand, _activeNode3, _activeNode4, _meta$isDragging2, _meta$isDragging3, _meta$toggleMenu, _meta$toggleMenu2, _meta$toggleMenu3, _meta$toggleMenu4, _meta$toggleMenu5, _meta$toggleMenu6, _meta$toggleMenu7, _meta$toggleMenu8, _meta$editorHeight, _meta$editorWidthLeft, _meta$editorWidthRigh, _meta$isPMDragging, _meta$isShiftDown, _meta$lastDragCancell;
237
+ export const apply = (api, formatMessage, tr, currentState, newState, flags, nodeViewPortalProviderAPI, nodeDecorationRegistry, rightSideControlsEnabled = false, anchorRectCache, resizeObserverWidth, pragmaticCleanup) => {
238
+ var _api$limitedMode, _api$limitedMode$shar, _api$limitedMode$shar2, _meta$multiSelectDnD, _api$editorViewMode, _api$editorViewMode$s, _activeNode, _activeNode2, _meta$activeNode$hand, _activeNode3, _activeNode4, _meta$isDragging2, _meta$isDragging3, _meta$toggleMenu, _meta$toggleMenu2, _meta$toggleMenu3, _meta$toggleMenu4, _meta$toggleMenu5, _meta$toggleMenu6, _meta$toggleMenu7, _meta$toggleMenu8, _meta$editorHeight, _meta$editorWidthLeft, _meta$editorWidthRigh, _meta$isPMDragging, _meta$isShiftDown, _meta$lastDragCancell;
239
239
  let {
240
240
  activeNode,
241
241
  decorations,
@@ -348,6 +348,7 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
348
348
  }
349
349
  const maybeNodeCountChanged = !isAllText && numReplaceSteps > 0;
350
350
  let latestActiveNode = meta === null || meta === void 0 ? void 0 : meta.activeNode;
351
+ const isViewMode = (api === null || api === void 0 ? void 0 : (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 ? void 0 : (_api$editorViewMode$s = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode$s === void 0 ? void 0 : _api$editorViewMode$s.mode) === 'view';
351
352
  if (!latestActiveNode && (!isActiveNodeDeleted || isReplacedWithSameSize)) {
352
353
  latestActiveNode = activeNode;
353
354
  }
@@ -443,12 +444,21 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
443
444
  decorations = decorations.remove(old);
444
445
  }
445
446
  }
447
+ if (rightSideControlsEnabled && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true) && isViewMode) {
448
+ for (const factory of nodeDecorationRegistry) {
449
+ if (factory.showInViewMode) {
450
+ var _activeNode1, _activeNode10;
451
+ 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);
452
+ decorations = decorations.remove(old);
453
+ }
454
+ }
455
+ }
446
456
  }
447
457
  } else if (api) {
448
- var _latestActiveNode5;
449
- if (shouldRecreateHandle) {
450
- var _activeNode1, _activeNode10, _latestActiveNode, _latestActiveNode2, _latestActiveNode3, _latestActiveNode4;
451
- const oldHandle = findHandleDec(decorations, (_activeNode1 = activeNode) === null || _activeNode1 === void 0 ? void 0 : _activeNode1.pos, (_activeNode10 = activeNode) === null || _activeNode10 === void 0 ? void 0 : _activeNode10.pos);
458
+ var _latestActiveNode5, _latestActiveNode14;
459
+ if (shouldRecreateHandle && (!rightSideControlsEnabled || !isViewMode)) {
460
+ var _activeNode11, _activeNode12, _latestActiveNode, _latestActiveNode2, _latestActiveNode3, _latestActiveNode4;
461
+ const oldHandle = findHandleDec(decorations, (_activeNode11 = activeNode) === null || _activeNode11 === void 0 ? void 0 : _activeNode11.pos, (_activeNode12 = activeNode) === null || _activeNode12 === void 0 ? void 0 : _activeNode12.pos);
452
462
  decorations = decorations.remove(oldHandle);
453
463
  const handleDec = dragHandleDecoration({
454
464
  api,
@@ -465,9 +475,9 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
465
475
  }
466
476
  if (shouldRecreateQuickInsertButton && ((_latestActiveNode5 = latestActiveNode) === null || _latestActiveNode5 === void 0 ? void 0 : _latestActiveNode5.rootPos) !== undefined &&
467
477
  // platform_editor_controls note: enables quick insert
468
- flags.toolbarFlagsEnabled) {
469
- var _activeNode11, _activeNode12, _latestActiveNode6, _latestActiveNode7, _latestActiveNode8, _latestActiveNode9, _latestActiveNode0;
470
- const oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode11 = activeNode) === null || _activeNode11 === void 0 ? void 0 : _activeNode11.rootPos, (_activeNode12 = activeNode) === null || _activeNode12 === void 0 ? void 0 : _activeNode12.rootPos);
478
+ flags.toolbarFlagsEnabled && (!rightSideControlsEnabled || !isViewMode)) {
479
+ var _activeNode13, _activeNode14, _latestActiveNode6, _latestActiveNode7, _latestActiveNode8, _latestActiveNode9, _latestActiveNode0;
480
+ const oldQuickInsertButton = findQuickInsertInsertButtonDecoration(decorations, (_activeNode13 = activeNode) === null || _activeNode13 === void 0 ? void 0 : _activeNode13.rootPos, (_activeNode14 = activeNode) === null || _activeNode14 === void 0 ? void 0 : _activeNode14.rootPos);
471
481
  decorations = decorations.remove(oldQuickInsertButton);
472
482
  const quickInsertButton = quickInsertButtonDecoration({
473
483
  api,
@@ -484,8 +494,8 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
484
494
  decorations = decorations.add(newState.doc, [quickInsertButton]);
485
495
  if (fg('platform_editor_expose_block_controls_deco_api')) {
486
496
  for (const factory of nodeDecorationRegistry) {
487
- var _activeNode13, _activeNode14, _latestActiveNode1, _latestActiveNode10, _latestActiveNode11, _latestActiveNode12, _latestActiveNode13;
488
- const old = decorations.find((_activeNode13 = activeNode) === null || _activeNode13 === void 0 ? void 0 : _activeNode13.rootPos, (_activeNode14 = activeNode) === null || _activeNode14 === void 0 ? void 0 : _activeNode14.rootPos, spec => spec.type === factory.type);
497
+ var _activeNode15, _activeNode16, _latestActiveNode1, _latestActiveNode10, _latestActiveNode11, _latestActiveNode12, _latestActiveNode13;
498
+ 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);
489
499
  decorations = decorations.remove(old);
490
500
  const dec = factory.create({
491
501
  editorState: newState,
@@ -500,6 +510,43 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
500
510
  }
501
511
  }
502
512
  }
513
+
514
+ // In view mode (edit/live pages), show right-side controls on block hover (without drag handle or quick insert)
515
+ if (isViewMode && ((_latestActiveNode14 = latestActiveNode) === null || _latestActiveNode14 === void 0 ? void 0 : _latestActiveNode14.rootPos) !== undefined && flags.toolbarFlagsEnabled && rightSideControlsEnabled && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true)) {
516
+ const rootPos = latestActiveNode.rootPos;
517
+ for (const factory of nodeDecorationRegistry) {
518
+ if (factory.showInViewMode) {
519
+ var _latestActiveNode15, _latestActiveNode16, _latestActiveNode17, _latestActiveNode18, _latestActiveNode19;
520
+ const existingAtPos = decorations.find(rootPos, rootPos, spec => spec.type === factory.type);
521
+ // Skip remove/re-add when decoration already exists at correct position - avoids
522
+ // flickering from widget destroy/recreate on every transaction (e.g. on hover).
523
+ if (existingAtPos.length > 0) {
524
+ continue;
525
+ }
526
+ // Remove any stale decoration at a different position (e.g. after moving to another block)
527
+ const stale = decorations.find(0, newState.doc.nodeSize, spec => spec.type === factory.type);
528
+ decorations = decorations.remove(stale);
529
+ const dec = factory.create({
530
+ editorState: newState,
531
+ nodeViewPortalProviderAPI,
532
+ anchorName: (_latestActiveNode15 = latestActiveNode) === null || _latestActiveNode15 === void 0 ? void 0 : _latestActiveNode15.anchorName,
533
+ nodeType: (_latestActiveNode16 = latestActiveNode) === null || _latestActiveNode16 === void 0 ? void 0 : _latestActiveNode16.nodeType,
534
+ rootPos: (_latestActiveNode17 = latestActiveNode) === null || _latestActiveNode17 === void 0 ? void 0 : _latestActiveNode17.rootPos,
535
+ rootAnchorName: (_latestActiveNode18 = latestActiveNode) === null || _latestActiveNode18 === void 0 ? void 0 : _latestActiveNode18.rootAnchorName,
536
+ rootNodeType: (_latestActiveNode19 = latestActiveNode) === null || _latestActiveNode19 === void 0 ? void 0 : _latestActiveNode19.rootNodeType
537
+ });
538
+ decorations = decorations.add(newState.doc, [dec]);
539
+ }
540
+ }
541
+ } else if (isViewMode && rightSideControlsEnabled && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true)) {
542
+ // Remove view-mode right-side decorations when no active node
543
+ for (const factory of nodeDecorationRegistry) {
544
+ if (factory.showInViewMode) {
545
+ const old = decorations.find(0, newState.doc.nodeSize, spec => spec.type === factory.type);
546
+ decorations = decorations.remove(old);
547
+ }
548
+ }
549
+ }
503
550
  }
504
551
 
505
552
  // Drop targets may be missing when the node count is being changed during a drag
@@ -547,12 +594,15 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
547
594
  let newActiveNode;
548
595
  // platform_editor_controls note: enables quick insert
549
596
  if (flags.toolbarFlagsEnabled) {
550
- var _latestActiveNode14, _latestActiveNode15;
597
+ var _latestActiveNode20, _latestActiveNode21;
551
598
  // remove isEmptyDoc check and let decorations render and determine their own visibility
552
- newActiveNode = !(meta !== null && meta !== void 0 && meta.activeNode) && findHandleDec(decorations, (_latestActiveNode14 = latestActiveNode) === null || _latestActiveNode14 === void 0 ? void 0 : _latestActiveNode14.pos, (_latestActiveNode15 = latestActiveNode) === null || _latestActiveNode15 === void 0 ? void 0 : _latestActiveNode15.pos).length === 0 ? null : latestActiveNode;
599
+ // In view mode with right-side controls we render node decorations (right-edge button), not the
600
+ // handle - so findHandleDec is always empty. Don't clear activeNode in that case.
601
+ const hasHandleOrViewModeControls = findHandleDec(decorations, (_latestActiveNode20 = latestActiveNode) === null || _latestActiveNode20 === void 0 ? void 0 : _latestActiveNode20.pos, (_latestActiveNode21 = latestActiveNode) === null || _latestActiveNode21 === void 0 ? void 0 : _latestActiveNode21.pos).length > 0 || isViewMode && rightSideControlsEnabled;
602
+ newActiveNode = meta !== null && meta !== void 0 && meta.editorBlurred || !(meta !== null && meta !== void 0 && meta.activeNode) && !hasHandleOrViewModeControls ? null : latestActiveNode;
553
603
  } else {
554
- var _latestActiveNode16, _latestActiveNode17;
555
- newActiveNode = isEmptyDoc || !(meta !== null && meta !== void 0 && meta.activeNode) && findHandleDec(decorations, (_latestActiveNode16 = latestActiveNode) === null || _latestActiveNode16 === void 0 ? void 0 : _latestActiveNode16.pos, (_latestActiveNode17 = latestActiveNode) === null || _latestActiveNode17 === void 0 ? void 0 : _latestActiveNode17.pos).length === 0 ? null : latestActiveNode;
604
+ var _latestActiveNode22, _latestActiveNode23;
605
+ newActiveNode = isEmptyDoc || !(meta !== null && meta !== void 0 && meta.activeNode) && findHandleDec(decorations, (_latestActiveNode22 = latestActiveNode) === null || _latestActiveNode22 === void 0 ? void 0 : _latestActiveNode22.pos, (_latestActiveNode23 = latestActiveNode) === null || _latestActiveNode23 === void 0 ? void 0 : _latestActiveNode23.pos).length === 0 ? null : latestActiveNode;
556
606
  }
557
607
  let isMenuOpenNew = isMenuOpen;
558
608
  if (expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
@@ -596,7 +646,7 @@ export const apply = (api, formatMessage, tr, currentState, newState, flags, nod
596
646
  isSelectedViaDragHandle: isSelectedViaDragHandleNew
597
647
  };
598
648
  };
599
- export const createPlugin = (api, getIntl, nodeViewPortalProviderAPI, nodeDecorationRegistry) => {
649
+ export const createPlugin = (api, getIntl, nodeViewPortalProviderAPI, nodeDecorationRegistry, rightSideControlsEnabled = false) => {
600
650
  const {
601
651
  formatMessage
602
652
  } = getIntl();
@@ -623,7 +673,7 @@ export const createPlugin = (api, getIntl, nodeViewPortalProviderAPI, nodeDecora
623
673
  init() {
624
674
  return initialState;
625
675
  },
626
- apply: (tr, currentState, _, newState) => apply(api, formatMessage, tr, currentState, newState, flags, nodeViewPortalProviderAPI, nodeDecorationRegistry, anchorRectCache, resizeObserverWidth, pragmaticCleanup)
676
+ apply: (tr, currentState, _, newState) => apply(api, formatMessage, tr, currentState, newState, flags, nodeViewPortalProviderAPI, nodeDecorationRegistry, rightSideControlsEnabled, anchorRectCache, resizeObserverWidth, pragmaticCleanup)
627
677
  },
628
678
  props: {
629
679
  decorations: state => {
@@ -633,7 +683,12 @@ export const createPlugin = (api, getIntl, nodeViewPortalProviderAPI, nodeDecora
633
683
  }
634
684
  const isDisabled = api === null || api === void 0 ? void 0 : (_api$editorDisabled = api.editorDisabled) === null || _api$editorDisabled === void 0 ? void 0 : (_api$editorDisabled$s = _api$editorDisabled.sharedState.currentState()) === null || _api$editorDisabled$s === void 0 ? void 0 : _api$editorDisabled$s.editorDisabled;
635
685
  if (isDisabled) {
636
- return;
686
+ var _api$editorViewMode2, _api$editorViewMode2$;
687
+ const remixRightSideEnabled = rightSideControlsEnabled && expValEquals('confluence_remix_icon_right_side', 'isEnabled', true);
688
+ // Hide decorations when disabled, except in view mode when right-side controls are enabled
689
+ if (!remixRightSideEnabled || (api === null || api === void 0 ? void 0 : (_api$editorViewMode2 = api.editorViewMode) === null || _api$editorViewMode2 === void 0 ? void 0 : (_api$editorViewMode2$ = _api$editorViewMode2.sharedState.currentState()) === null || _api$editorViewMode2$ === void 0 ? void 0 : _api$editorViewMode2$.mode) !== 'view') {
690
+ return;
691
+ }
637
692
  }
638
693
  return (_key$getState2 = key.getState(state)) === null || _key$getState2 === void 0 ? void 0 : _key$getState2.decorations;
639
694
  },