@atlaskit/editor-plugin-block-menu 20.0.3 → 20.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @atlaskit/editor-plugin-block-menu
2
2
 
3
+ ## 20.0.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [`2fbdbadc1a47c`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2fbdbadc1a47c) -
8
+ Convert direct container headings and nested list paragraphs to Normal or Small text while
9
+ preserving other nested content under platform_editor_block_menu_small_text.
10
+ - Updated dependencies
11
+
12
+ ## 20.0.4
13
+
14
+ ### Patch Changes
15
+
16
+ - [`981dd11bb4ed2`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/981dd11bb4ed2) -
17
+ Add opt-in target visibility handling to editor popups, and use it to hide the Jira description
18
+ block menu while its originating node is outside the visible viewport. The popup reappears when
19
+ its target returns, preserving an open nested menu, behind the
20
+ `platform_editor_popup_target_visibility` experiment.
21
+ - Updated dependencies
22
+
3
23
  ## 20.0.3
4
24
 
5
25
  ### Patch Changes
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.TRANSFORMATION_MATRIX_PANEL_C1 = exports.TRANSFORMATION_MATRIX = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
9
10
  var _applyTargetTextTypeStep = require("./steps/applyTargetTextTypeStep");
10
11
  var _convertEachNodeStep = require("./steps/convertEachNodeStep");
11
12
  var _decisionListToListStep = require("./steps/decisionListToListStep");
@@ -24,6 +25,19 @@ var _wrapMixedContentStep = require("./steps/wrapMixedContentStep");
24
25
  var _wrapStep = require("./steps/wrapStep");
25
26
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
26
27
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
28
+ var applyTargetHeadingToParagraphStep = function applyTargetHeadingToParagraphStep(nodes, context) {
29
+ if (!(0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_block_menu_small_text')) {
30
+ return nodes;
31
+ }
32
+ return nodes.map(function (node) {
33
+ var _applyTargetTextTypeS;
34
+ if (node.type.name !== 'heading') {
35
+ return node;
36
+ }
37
+ return (_applyTargetTextTypeS = (0, _applyTargetTextTypeStep.applyTargetTextTypeStep)([node], context).at(0)) !== null && _applyTargetTextTypeS !== void 0 ? _applyTargetTextTypeS : node;
38
+ });
39
+ };
40
+
27
41
  /**
28
42
  * Creates the transformation matrix for all node type pairs.
29
43
  * When includePanelC1 is true (platform_editor_nest_table_in_panel experiment on),
@@ -71,7 +85,7 @@ var createTransformationMatrix = function createTransformationMatrix(includePane
71
85
  expand: [_unwrapStep.unwrapStep, _wrapStep.wrapStep],
72
86
  nestedExpand: [_unwrapStep.unwrapStep, _wrapStep.wrapStep],
73
87
  layoutSection: [_unwrapStep.unwrapStep, _wrapMixedContentStep.wrapMixedContentStep],
74
- paragraph: [_unwrapStep.unwrapStep]
88
+ paragraph: [_unwrapStep.unwrapStep, applyTargetHeadingToParagraphStep]
75
89
  },
76
90
  panel_c1: includePanelC1 ? {
77
91
  blockquote: [_unwrapStep.unwrapStep, _wrapMixedContentStep.wrapMixedContentStep],
@@ -79,7 +93,7 @@ var createTransformationMatrix = function createTransformationMatrix(includePane
79
93
  expand: [_unwrapStep.unwrapStep, _wrapStep.wrapStep],
80
94
  nestedExpand: [_unwrapStep.unwrapStep, _wrapStep.wrapStep],
81
95
  layoutSection: [_unwrapStep.unwrapStep, _wrapMixedContentStep.wrapMixedContentStep],
82
- paragraph: [_unwrapStep.unwrapStep]
96
+ paragraph: [_unwrapStep.unwrapStep, applyTargetHeadingToParagraphStep]
83
97
  } : {},
84
98
  expand: _objectSpread(_objectSpread({
85
99
  panel: [_unwrapExpandStep.unwrapExpandStep, _wrapMixedContentStep.wrapMixedContentStep]
@@ -89,7 +103,7 @@ var createTransformationMatrix = function createTransformationMatrix(includePane
89
103
  blockquote: [_unwrapExpandStep.unwrapExpandStep, _wrapMixedContentStep.wrapMixedContentStep],
90
104
  layoutSection: [_unwrapExpandStep.unwrapExpandStep, _wrapMixedContentStep.wrapMixedContentStep],
91
105
  nestedExpand: [_unwrapExpandStep.unwrapExpandStep, _wrapStep.wrapStep],
92
- paragraph: [_unwrapExpandStep.unwrapExpandStep]
106
+ paragraph: [_unwrapExpandStep.unwrapExpandStep, applyTargetHeadingToParagraphStep]
93
107
  }),
94
108
  nestedExpand: _objectSpread(_objectSpread({
95
109
  panel: [_unwrapExpandStep.unwrapExpandStep, _wrapMixedContentStep.wrapMixedContentStep]
@@ -98,7 +112,7 @@ var createTransformationMatrix = function createTransformationMatrix(includePane
98
112
  } : {}), {}, {
99
113
  blockquote: [_unwrapExpandStep.unwrapExpandStep, _wrapMixedContentStep.wrapMixedContentStep],
100
114
  layoutSection: [_unwrapExpandStep.unwrapExpandStep, _wrapMixedContentStep.wrapMixedContentStep],
101
- paragraph: [_unwrapExpandStep.unwrapExpandStep]
115
+ paragraph: [_unwrapExpandStep.unwrapExpandStep, applyTargetHeadingToParagraphStep]
102
116
  }),
103
117
  blockquote: _objectSpread(_objectSpread({
104
118
  expand: [_wrapStep.wrapStep],
@@ -108,7 +122,7 @@ var createTransformationMatrix = function createTransformationMatrix(includePane
108
122
  }, includePanelC1 ? {
109
123
  panel_c1: [_unwrapStep.unwrapStep, _wrapStep.wrapStep]
110
124
  } : {}), {}, {
111
- paragraph: [_unwrapStep.unwrapStep],
125
+ paragraph: [_unwrapStep.unwrapStep, applyTargetHeadingToParagraphStep],
112
126
  decisionList: [_unwrapStep.unwrapStep, _wrapBlockquoteToDecisionListStep.wrapBlockquoteToDecisionListStep]
113
127
  }),
114
128
  layoutSection: _objectSpread(_objectSpread({
@@ -119,7 +133,7 @@ var createTransformationMatrix = function createTransformationMatrix(includePane
119
133
  }, includePanelC1 ? {
120
134
  panel_c1: [_unwrapLayoutStep.unwrapLayoutStep, _wrapMixedContentStep.wrapMixedContentStep]
121
135
  } : {}), {}, {
122
- paragraph: [_unwrapLayoutStep.unwrapLayoutStep]
136
+ paragraph: [_unwrapLayoutStep.unwrapLayoutStep, applyTargetHeadingToParagraphStep]
123
137
  }),
124
138
  codeBlock: _objectSpread(_objectSpread({
125
139
  blockquote: [_wrapStep.wrapStep],
@@ -13,7 +13,14 @@ var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
13
13
  var _utils = require("../transform-node-utils/utils");
14
14
  var _TRANSFORMATION_MATRIX = require("./TRANSFORMATION_MATRIX");
15
15
  var _types = require("./types");
16
+ var LIST_NODE_TYPE_NAMES = new Set(['bulletList', 'orderedList', 'taskList', 'decisionList', 'listItem', 'taskItem', 'blockTaskItem', 'decisionItem']);
17
+ var shouldRecurseThroughListNode = function shouldRecurseThroughListNode(node) {
18
+ return LIST_NODE_TYPE_NAMES.has(node.type.name);
19
+ };
16
20
  var _applyTargetNodeMarks = function applyTargetNodeMarks(node, targetNodeType, marksToAdd, marksToRemove, schema) {
21
+ var shouldRecurseIntoChildren = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : function () {
22
+ return true;
23
+ };
17
24
  var nextNode = node;
18
25
  if (node.type === targetNodeType) {
19
26
  var marksAfterRemovals = (marksToRemove !== null && marksToRemove !== void 0 ? marksToRemove : []).reduce(function (currentMarks, name) {
@@ -29,12 +36,12 @@ var _applyTargetNodeMarks = function applyTargetNodeMarks(node, targetNodeType,
29
36
  }, marksAfterRemovals);
30
37
  nextNode = node.mark(marks);
31
38
  }
32
- if (nextNode.childCount === 0) {
39
+ if (!shouldRecurseIntoChildren(nextNode) || nextNode.childCount === 0) {
33
40
  return nextNode;
34
41
  }
35
42
  var children = [];
36
43
  nextNode.forEach(function (child) {
37
- return children.push(_applyTargetNodeMarks(child, targetNodeType, marksToAdd, marksToRemove, schema));
44
+ return children.push(_applyTargetNodeMarks(child, targetNodeType, marksToAdd, marksToRemove, schema, shouldRecurseIntoChildren));
38
45
  });
39
46
  return nextNode.copy(_model.Fragment.fromArray(children));
40
47
  };
@@ -112,12 +119,13 @@ var convertNodesToTargetType = exports.convertNodesToTargetType = function conve
112
119
  }, sourceNodes);
113
120
  return upgradePanelNodesToPanelC1(_resultNodes, parentNode, schema);
114
121
  }
122
+ var shouldLimitTargetNodeMarkRecursion = targetNodeTypeName === 'paragraph' && ['multi', 'panel', 'panel_c1', 'expand', 'nestedExpand', 'blockquote', 'layoutSection'].includes(selectedNodeTypeName);
115
123
  var resultNodes = (_steps$reduce = steps === null || steps === void 0 ? void 0 : steps.reduce(function (nodes, step) {
116
124
  return step(nodes, context);
117
125
  }, sourceNodes)) !== null && _steps$reduce !== void 0 ? _steps$reduce : sourceNodes;
118
126
  var upgradedNodes = steps !== null && steps !== void 0 && steps.length ? upgradePanelNodesToPanelC1(resultNodes, parentNode, schema) : resultNodes;
119
127
  return upgradedNodes.map(function (node) {
120
- return _applyTargetNodeMarks(node, targetNodeType, marksToAdd, marksToRemove, schema);
128
+ return _applyTargetNodeMarks(node, targetNodeType, marksToAdd, marksToRemove, schema, shouldLimitTargetNodeMarkRecursion ? shouldRecurseThroughListNode : undefined);
121
129
  });
122
130
  };
123
131
  var isTransformDisabledBasedOnStepsConfig = exports.isTransformDisabledBasedOnStepsConfig = function isTransformDisabledBasedOnStepsConfig(selectedNodeType, targetNodeType) {
@@ -10,11 +10,14 @@ var _reactIntl = require("react-intl");
10
10
  var _messages = require("@atlaskit/editor-common/messages");
11
11
  var _editorToolbar = require("@atlaskit/editor-toolbar");
12
12
  var _chevronRight = _interopRequireDefault(require("@atlaskit/icon/core/chevron-right"));
13
+ var _blockMenuTargetVisibilityContext = require("../block-menu-target-visibility-context");
13
14
  var FallbackNestedMenu = function FallbackNestedMenu(_ref) {
14
15
  var children = _ref.children;
15
16
  var _useIntl = (0, _reactIntl.useIntl)(),
16
17
  formatMessage = _useIntl.formatMessage;
18
+ var targetVisible = (0, _blockMenuTargetVisibilityContext.useBlockMenuTargetVisibility)();
17
19
  return /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarNestedDropdownMenu, {
20
+ isPopupVisible: targetVisible,
18
21
  elemBefore: undefined,
19
22
  elemAfter: /*#__PURE__*/_react.default.createElement(_chevronRight.default, {
20
23
  label: "",
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useBlockMenuTargetVisibility = exports.BlockMenuTargetVisibilityProvider = void 0;
7
+ var _react = require("react");
8
+ var BlockMenuTargetVisibilityContext = /*#__PURE__*/(0, _react.createContext)(undefined);
9
+ var BlockMenuTargetVisibilityProvider = exports.BlockMenuTargetVisibilityProvider = BlockMenuTargetVisibilityContext.Provider;
10
+ var useBlockMenuTargetVisibility = exports.useBlockMenuTargetVisibility = function useBlockMenuTargetVisibility() {
11
+ return (0, _react.useContext)(BlockMenuTargetVisibilityContext);
12
+ };
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "__esModule", {
9
9
  exports.default = void 0;
10
10
  require("./block-menu.compiled.css");
11
11
  var _runtime = require("@compiled/react/runtime");
12
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
12
13
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
13
14
  var _react = _interopRequireWildcard(require("react"));
14
15
  var _reactIntl = require("react-intl");
@@ -30,7 +31,10 @@ var _redo = require("@atlaskit/prosemirror-history/redo");
30
31
  var _undo = require("@atlaskit/prosemirror-history/undo");
31
32
  var _blockMenuProvider = require("./block-menu-provider");
32
33
  var _BlockMenuRenderer = require("./block-menu-renderer/BlockMenuRenderer");
34
+ var _blockMenuTargetVisibilityContext = require("./block-menu-target-visibility-context");
33
35
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
36
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
37
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
34
38
  var styles = {
35
39
  base: "_2rko12b0 _bfhk1bhr _16qs130s",
36
40
  maxWidthStyles: "_p12fnklw",
@@ -180,17 +184,20 @@ var BlockMenu = function BlockMenu(_ref5) {
180
184
  mountTo = _ref5.mountTo,
181
185
  boundariesElement = _ref5.boundariesElement,
182
186
  scrollableElement = _ref5.scrollableElement;
187
+ var isPopupTargetVisibilityEnabled = (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_popup_target_visibility');
183
188
  var _useSharedPluginState = (0, _hooks.useSharedPluginStateWithSelector)(api, ['blockControls', 'userIntent'], function (states) {
184
- var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$userIntentSta, _states$blockControls4;
189
+ var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$blockControls4, _states$userIntentSta, _states$blockControls5;
185
190
  return {
186
191
  menuTriggerBy: (_states$blockControls = states.blockControlsState) === null || _states$blockControls === void 0 ? void 0 : _states$blockControls.menuTriggerBy,
187
- isSelectedViaDragHandle: (_states$blockControls2 = states.blockControlsState) === null || _states$blockControls2 === void 0 ? void 0 : _states$blockControls2.isSelectedViaDragHandle,
188
- isMenuOpen: (_states$blockControls3 = states.blockControlsState) === null || _states$blockControls3 === void 0 ? void 0 : _states$blockControls3.isMenuOpen,
192
+ menuTriggerByNode: isPopupTargetVisibilityEnabled ? (_states$blockControls2 = states.blockControlsState) === null || _states$blockControls2 === void 0 ? void 0 : _states$blockControls2.menuTriggerByNode : undefined,
193
+ isSelectedViaDragHandle: (_states$blockControls3 = states.blockControlsState) === null || _states$blockControls3 === void 0 ? void 0 : _states$blockControls3.isSelectedViaDragHandle,
194
+ isMenuOpen: (_states$blockControls4 = states.blockControlsState) === null || _states$blockControls4 === void 0 ? void 0 : _states$blockControls4.isMenuOpen,
189
195
  currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent,
190
- openedViaKeyboard: (_states$blockControls4 = states.blockControlsState) === null || _states$blockControls4 === void 0 || (_states$blockControls4 = _states$blockControls4.blockMenuOptions) === null || _states$blockControls4 === void 0 ? void 0 : _states$blockControls4.openedViaKeyboard
196
+ openedViaKeyboard: (_states$blockControls5 = states.blockControlsState) === null || _states$blockControls5 === void 0 || (_states$blockControls5 = _states$blockControls5.blockMenuOptions) === null || _states$blockControls5 === void 0 ? void 0 : _states$blockControls5.openedViaKeyboard
191
197
  };
192
198
  }),
193
199
  menuTriggerBy = _useSharedPluginState.menuTriggerBy,
200
+ menuTriggerByNode = _useSharedPluginState.menuTriggerByNode,
194
201
  isSelectedViaDragHandle = _useSharedPluginState.isSelectedViaDragHandle,
195
202
  isMenuOpen = _useSharedPluginState.isMenuOpen,
196
203
  currentUserIntent = _useSharedPluginState.currentUserIntent,
@@ -228,6 +235,8 @@ var BlockMenu = function BlockMenu(_ref5) {
228
235
  anchoredToRef.current = menuTriggerBy;
229
236
  }
230
237
  var targetHandleRef = (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_block_control_migration') ? (_openMenuHandleRef$cu = openMenuHandleRef.current) !== null && _openMenuHandleRef$cu !== void 0 ? _openMenuHandleRef$cu : surfaceDragHandle : editorView === null || editorView === void 0 || (_editorView$dom = editorView.dom) === null || _editorView$dom === void 0 ? void 0 : _editorView$dom.querySelector(_styles.DRAG_HANDLE_SELECTOR);
238
+ var menuTargetNode = menuTriggerByNode && (editorView === null || editorView === void 0 ? void 0 : editorView.nodeDOM(menuTriggerByNode.pos));
239
+ var visibilityTarget = menuTargetNode instanceof HTMLElement ? menuTargetNode : undefined;
231
240
  var closeMenu = _react.default.useCallback(function () {
232
241
  api === null || api === void 0 || api.core.actions.execute(function (_ref7) {
233
242
  var _api$blockControls, _api$userIntent2;
@@ -257,19 +266,71 @@ var BlockMenu = function BlockMenu(_ref5) {
257
266
  _React$useState4 = (0, _slicedToArray2.default)(_React$useState3, 2),
258
267
  menuHeight = _React$useState4[0],
259
268
  setMenuHeight = _React$useState4[1];
269
+ var _React$useState5 = _react.default.useState({
270
+ isVisible: undefined,
271
+ restoreFocusTo: null
272
+ }),
273
+ _React$useState6 = (0, _slicedToArray2.default)(_React$useState5, 2),
274
+ targetVisibility = _React$useState6[0],
275
+ setTargetVisibility = _React$useState6[1];
276
+ var targetVisible = targetVisibility.isVisible,
277
+ restoreFocusTo = targetVisibility.restoreFocusTo;
278
+ // CSS visibility can move focus to the document body. Preserve the active menu item so the
279
+ // existing focus-loss guard does not close the menus during a temporary scroll transition.
280
+ var handleTargetVisibilityChanged = _react.default.useCallback(function (isVisible) {
281
+ var _popupRef$current;
282
+ if (isVisible) {
283
+ setTargetVisibility(function (current) {
284
+ return {
285
+ isVisible: true,
286
+ restoreFocusTo: current.isVisible === false ? current.restoreFocusTo : null
287
+ };
288
+ });
289
+ return;
290
+ }
291
+
292
+ // Capture focus before scheduling the state update so the updater stays pure.
293
+ // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
294
+ var activeElement = document.activeElement;
295
+ var isFocusInMenu = activeElement instanceof HTMLElement && (((_popupRef$current = popupRef.current) === null || _popupRef$current === void 0 ? void 0 : _popupRef$current.contains(activeElement)) || activeElement.closest(_styles.NESTED_DROPDOWN_MENU) !== null);
296
+ setTargetVisibility({
297
+ isVisible: false,
298
+ restoreFocusTo: isFocusInMenu ? activeElement : null
299
+ });
300
+ }, []);
301
+ _react.default.useLayoutEffect(function () {
302
+ if (!isPopupTargetVisibilityEnabled || !targetVisible || !restoreFocusTo) {
303
+ return;
304
+ }
305
+ var _restoreFocusTo$owner = restoreFocusTo.ownerDocument,
306
+ activeElement = _restoreFocusTo$owner.activeElement,
307
+ body = _restoreFocusTo$owner.body,
308
+ documentElement = _restoreFocusTo$owner.documentElement;
309
+ var focusWasLost = activeElement === null || activeElement === body || activeElement === documentElement;
310
+ if (restoreFocusTo.isConnected && focusWasLost) {
311
+ restoreFocusTo.focus({
312
+ preventScroll: true
313
+ });
314
+ }
315
+ setTargetVisibility(function (current) {
316
+ return _objectSpread(_objectSpread({}, current), {}, {
317
+ restoreFocusTo: null
318
+ });
319
+ });
320
+ }, [isPopupTargetVisibilityEnabled, restoreFocusTo, targetVisible]);
260
321
  var targetHandleHeightOffset = -((targetHandleRef === null || targetHandleRef === void 0 ? void 0 : targetHandleRef.clientHeight) || 0);
261
322
  _react.default.useLayoutEffect(function () {
262
- var _popupRef$current;
323
+ var _popupRef$current2;
263
324
  if (!isMenuOpen) {
264
325
  return;
265
326
  }
266
327
  onDropdownOpenChanged(true);
267
- setMenuHeight(((_popupRef$current = popupRef.current) === null || _popupRef$current === void 0 ? void 0 : _popupRef$current.clientHeight) || FALLBACK_MENU_HEIGHT);
328
+ setMenuHeight(((_popupRef$current2 = popupRef.current) === null || _popupRef$current2 === void 0 ? void 0 : _popupRef$current2.clientHeight) || FALLBACK_MENU_HEIGHT);
268
329
  }, [isMenuOpen, onDropdownOpenChanged]);
269
330
 
270
331
  // Nested dropdowns are portaled outside the block-menu popup. Sparse surface reconciliation
271
332
  // dispatches after the menu opens, so a re-render can occur while focus is inside that portal.
272
- var hasFocus = (_ref8 = (editorView === null || editorView === void 0 ? void 0 : editorView.hasFocus()) ||
333
+ var hasFocus = (_ref8 = (editorView === null || editorView === void 0 ? void 0 : editorView.hasFocus()) || isPopupTargetVisibilityEnabled && (targetVisible === false || restoreFocusTo !== null) ||
273
334
  // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
274
335
  document.activeElement === targetHandleRef || popupRef.current && (
275
336
  // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
@@ -340,6 +401,10 @@ var BlockMenu = function BlockMenu(_ref5) {
340
401
  if (!(targetHandleRef instanceof HTMLElement)) {
341
402
  return null;
342
403
  }
404
+ var blockMenuContent = /*#__PURE__*/_react.default.createElement(BlockMenuContent, {
405
+ api: api,
406
+ setRef: setRef
407
+ });
343
408
  return /*#__PURE__*/_react.default.createElement(_errorBoundary.ErrorBoundary, {
344
409
  component: _analytics.ACTION_SUBJECT.BLOCK_MENU,
345
410
  dispatchAnalyticsEvent: api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions.fireAnalyticsEvent,
@@ -354,11 +419,14 @@ var BlockMenu = function BlockMenu(_ref5) {
354
419
  boundariesElement: boundariesElement,
355
420
  scrollableElement: scrollableElement,
356
421
  target: targetHandleRef,
422
+ visibilityTarget: visibilityTarget,
357
423
  zIndex: _editorSharedStyles.akEditorFloatingOverlapPanelZIndex,
358
424
  fitWidth: DEFAULT_MENU_WIDTH,
359
425
  fitHeight: menuHeight,
360
426
  preventOverflow: true,
361
- stick: true
427
+ stick: true,
428
+ hideWhenTargetOutOfView: isPopupTargetVisibilityEnabled,
429
+ onTargetVisibilityChanged: isPopupTargetVisibilityEnabled ? handleTargetVisibilityChanged : undefined
362
430
  // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
363
431
  ,
364
432
  offset: [_styles.DRAG_HANDLE_WIDTH + DRAG_HANDLE_OFFSET_PADDING, targetHandleHeightOffset],
@@ -367,10 +435,9 @@ var BlockMenu = function BlockMenu(_ref5) {
367
435
  {
368
436
  initialFocus: undefined
369
437
  } : undefined
370
- }, /*#__PURE__*/_react.default.createElement(BlockMenuContent, {
371
- api: api,
372
- setRef: setRef
373
- })));
438
+ }, isPopupTargetVisibilityEnabled ? /*#__PURE__*/_react.default.createElement(_blockMenuTargetVisibilityContext.BlockMenuTargetVisibilityProvider, {
439
+ value: targetVisible
440
+ }, blockMenuContent) : blockMenuContent));
374
441
  };
375
442
 
376
443
  // eslint-disable-next-line @typescript-eslint/no-restricted-types
@@ -14,6 +14,7 @@ var _editorToolbar = require("@atlaskit/editor-toolbar");
14
14
  var _changes = _interopRequireDefault(require("@atlaskit/icon/core/changes"));
15
15
  var _chevronRight = _interopRequireDefault(require("@atlaskit/icon/core/chevron-right"));
16
16
  var _fg = require("@atlaskit/platform-feature-flags/fg");
17
+ var _blockMenuTargetVisibilityContext = require("./block-menu-target-visibility-context");
17
18
  var _consts = require("./consts");
18
19
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
19
20
  var BLOCK_MENU_TRANSFORM_SPOTLIGHT_PORTAL_SELECTOR = '[data-test-id="block-menu-transform-spotlight-portal-container"]';
@@ -25,6 +26,7 @@ var FormatMenuComponent = exports.FormatMenuComponent = function FormatMenuCompo
25
26
  children = _ref.children;
26
27
  var _useIntl = (0, _reactIntl.useIntl)(),
27
28
  formatMessage = _useIntl.formatMessage;
29
+ var targetVisible = (0, _blockMenuTargetVisibilityContext.useBlockMenuTargetVisibility)();
28
30
  var formatMenuLabel = _messages.blockMenuMessages.changeFormat;
29
31
  var handleClick = (0, _react.useCallback)(function () {
30
32
  api === null || api === void 0 || api.core.actions.execute(function (_ref2) {
@@ -43,6 +45,7 @@ var FormatMenuComponent = exports.FormatMenuComponent = function FormatMenuCompo
43
45
  });
44
46
  }, [api]);
45
47
  return /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarNestedDropdownMenu, {
48
+ isPopupVisible: targetVisible,
46
49
  text: formatMessage(formatMenuLabel),
47
50
  elemBefore: /*#__PURE__*/_react.default.createElement(_changes.default, {
48
51
  label: "",
@@ -1,3 +1,4 @@
1
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
1
2
  import { applyTargetTextTypeStep } from './steps/applyTargetTextTypeStep';
2
3
  import { convertEachNodeStep } from './steps/convertEachNodeStep';
3
4
  import { decisionListToListStep } from './steps/decisionListToListStep';
@@ -14,6 +15,19 @@ import { wrapBlockquoteToDecisionListStep } from './steps/wrapBlockquoteToDecisi
14
15
  import { wrapIntoListStep } from './steps/wrapIntoListStep';
15
16
  import { wrapMixedContentStep } from './steps/wrapMixedContentStep';
16
17
  import { wrapStep } from './steps/wrapStep';
18
+ const applyTargetHeadingToParagraphStep = (nodes, context) => {
19
+ if (!isExperimentEnabled('platform_editor_block_menu_small_text')) {
20
+ return nodes;
21
+ }
22
+ return nodes.map(node => {
23
+ var _applyTargetTextTypeS;
24
+ if (node.type.name !== 'heading') {
25
+ return node;
26
+ }
27
+ return (_applyTargetTextTypeS = applyTargetTextTypeStep([node], context).at(0)) !== null && _applyTargetTextTypeS !== void 0 ? _applyTargetTextTypeS : node;
28
+ });
29
+ };
30
+
17
31
  /**
18
32
  * Creates the transformation matrix for all node type pairs.
19
33
  * When includePanelC1 is true (platform_editor_nest_table_in_panel experiment on),
@@ -60,7 +74,7 @@ const createTransformationMatrix = includePanelC1 => ({
60
74
  expand: [unwrapStep, wrapStep],
61
75
  nestedExpand: [unwrapStep, wrapStep],
62
76
  layoutSection: [unwrapStep, wrapMixedContentStep],
63
- paragraph: [unwrapStep]
77
+ paragraph: [unwrapStep, applyTargetHeadingToParagraphStep]
64
78
  },
65
79
  panel_c1: includePanelC1 ? {
66
80
  blockquote: [unwrapStep, wrapMixedContentStep],
@@ -68,7 +82,7 @@ const createTransformationMatrix = includePanelC1 => ({
68
82
  expand: [unwrapStep, wrapStep],
69
83
  nestedExpand: [unwrapStep, wrapStep],
70
84
  layoutSection: [unwrapStep, wrapMixedContentStep],
71
- paragraph: [unwrapStep]
85
+ paragraph: [unwrapStep, applyTargetHeadingToParagraphStep]
72
86
  } : {},
73
87
  expand: {
74
88
  panel: [unwrapExpandStep, wrapMixedContentStep],
@@ -78,7 +92,7 @@ const createTransformationMatrix = includePanelC1 => ({
78
92
  blockquote: [unwrapExpandStep, wrapMixedContentStep],
79
93
  layoutSection: [unwrapExpandStep, wrapMixedContentStep],
80
94
  nestedExpand: [unwrapExpandStep, wrapStep],
81
- paragraph: [unwrapExpandStep]
95
+ paragraph: [unwrapExpandStep, applyTargetHeadingToParagraphStep]
82
96
  },
83
97
  nestedExpand: {
84
98
  panel: [unwrapExpandStep, wrapMixedContentStep],
@@ -87,7 +101,7 @@ const createTransformationMatrix = includePanelC1 => ({
87
101
  } : {}),
88
102
  blockquote: [unwrapExpandStep, wrapMixedContentStep],
89
103
  layoutSection: [unwrapExpandStep, wrapMixedContentStep],
90
- paragraph: [unwrapExpandStep]
104
+ paragraph: [unwrapExpandStep, applyTargetHeadingToParagraphStep]
91
105
  },
92
106
  blockquote: {
93
107
  expand: [wrapStep],
@@ -97,7 +111,7 @@ const createTransformationMatrix = includePanelC1 => ({
97
111
  ...(includePanelC1 ? {
98
112
  panel_c1: [unwrapStep, wrapStep]
99
113
  } : {}),
100
- paragraph: [unwrapStep],
114
+ paragraph: [unwrapStep, applyTargetHeadingToParagraphStep],
101
115
  decisionList: [unwrapStep, wrapBlockquoteToDecisionListStep]
102
116
  },
103
117
  layoutSection: {
@@ -108,7 +122,7 @@ const createTransformationMatrix = includePanelC1 => ({
108
122
  ...(includePanelC1 ? {
109
123
  panel_c1: [unwrapLayoutStep, wrapMixedContentStep]
110
124
  } : {}),
111
- paragraph: [unwrapLayoutStep]
125
+ paragraph: [unwrapLayoutStep, applyTargetHeadingToParagraphStep]
112
126
  },
113
127
  codeBlock: {
114
128
  blockquote: [wrapStep],
@@ -5,7 +5,9 @@ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
5
5
  import { getTargetNodeTypeNameInContext } from '../transform-node-utils/utils';
6
6
  import { TRANSFORMATION_MATRIX, TRANSFORMATION_MATRIX_PANEL_C1 } from './TRANSFORMATION_MATRIX';
7
7
  import { getNodeName, toNodeTypeValue } from './types';
8
- const applyTargetNodeMarks = (node, targetNodeType, marksToAdd, marksToRemove, schema) => {
8
+ const LIST_NODE_TYPE_NAMES = new Set(['bulletList', 'orderedList', 'taskList', 'decisionList', 'listItem', 'taskItem', 'blockTaskItem', 'decisionItem']);
9
+ const shouldRecurseThroughListNode = node => LIST_NODE_TYPE_NAMES.has(node.type.name);
10
+ const applyTargetNodeMarks = (node, targetNodeType, marksToAdd, marksToRemove, schema, shouldRecurseIntoChildren = () => true) => {
9
11
  let nextNode = node;
10
12
  if (node.type === targetNodeType) {
11
13
  const marksAfterRemovals = (marksToRemove !== null && marksToRemove !== void 0 ? marksToRemove : []).reduce((currentMarks, name) => {
@@ -18,11 +20,11 @@ const applyTargetNodeMarks = (node, targetNodeType, marksToAdd, marksToRemove, s
18
20
  }, marksAfterRemovals);
19
21
  nextNode = node.mark(marks);
20
22
  }
21
- if (nextNode.childCount === 0) {
23
+ if (!shouldRecurseIntoChildren(nextNode) || nextNode.childCount === 0) {
22
24
  return nextNode;
23
25
  }
24
26
  const children = [];
25
- nextNode.forEach(child => children.push(applyTargetNodeMarks(child, targetNodeType, marksToAdd, marksToRemove, schema)));
27
+ nextNode.forEach(child => children.push(applyTargetNodeMarks(child, targetNodeType, marksToAdd, marksToRemove, schema, shouldRecurseIntoChildren)));
26
28
  return nextNode.copy(Fragment.fromArray(children));
27
29
  };
28
30
 
@@ -98,9 +100,10 @@ export const convertNodesToTargetType = ({
98
100
  const resultNodes = steps.reduce((nodes, step) => step(nodes, context), sourceNodes);
99
101
  return upgradePanelNodesToPanelC1(resultNodes, parentNode, schema);
100
102
  }
103
+ const shouldLimitTargetNodeMarkRecursion = targetNodeTypeName === 'paragraph' && ['multi', 'panel', 'panel_c1', 'expand', 'nestedExpand', 'blockquote', 'layoutSection'].includes(selectedNodeTypeName);
101
104
  const resultNodes = (_steps$reduce = steps === null || steps === void 0 ? void 0 : steps.reduce((nodes, step) => step(nodes, context), sourceNodes)) !== null && _steps$reduce !== void 0 ? _steps$reduce : sourceNodes;
102
105
  const upgradedNodes = steps !== null && steps !== void 0 && steps.length ? upgradePanelNodesToPanelC1(resultNodes, parentNode, schema) : resultNodes;
103
- return upgradedNodes.map(node => applyTargetNodeMarks(node, targetNodeType, marksToAdd, marksToRemove, schema));
106
+ return upgradedNodes.map(node => applyTargetNodeMarks(node, targetNodeType, marksToAdd, marksToRemove, schema, shouldLimitTargetNodeMarkRecursion ? shouldRecurseThroughListNode : undefined));
104
107
  };
105
108
  export const isTransformDisabledBasedOnStepsConfig = (selectedNodeType, targetNodeType) => {
106
109
  const steps = expValEquals('platform_editor_nest_table_in_panel', 'isEnabled', true) ? TRANSFORMATION_MATRIX_PANEL_C1[selectedNodeType][targetNodeType] : TRANSFORMATION_MATRIX[selectedNodeType][targetNodeType];
@@ -3,13 +3,16 @@ import { useIntl } from 'react-intl';
3
3
  import { blockMenuMessages } from '@atlaskit/editor-common/messages';
4
4
  import { ToolbarDropdownItem, ToolbarDropdownItemSection, ToolbarNestedDropdownMenu } from '@atlaskit/editor-toolbar';
5
5
  import ChevronRightIcon from '@atlaskit/icon/core/chevron-right';
6
+ import { useBlockMenuTargetVisibility } from '../block-menu-target-visibility-context';
6
7
  const FallbackNestedMenu = ({
7
8
  children
8
9
  }) => {
9
10
  const {
10
11
  formatMessage
11
12
  } = useIntl();
13
+ const targetVisible = useBlockMenuTargetVisibility();
12
14
  return /*#__PURE__*/React.createElement(ToolbarNestedDropdownMenu, {
15
+ isPopupVisible: targetVisible,
13
16
  elemBefore: undefined,
14
17
  elemAfter: /*#__PURE__*/React.createElement(ChevronRightIcon, {
15
18
  label: "",
@@ -0,0 +1,4 @@
1
+ import { createContext, useContext } from 'react';
2
+ const BlockMenuTargetVisibilityContext = /*#__PURE__*/createContext(undefined);
3
+ export const BlockMenuTargetVisibilityProvider = BlockMenuTargetVisibilityContext.Provider;
4
+ export const useBlockMenuTargetVisibility = () => useContext(BlockMenuTargetVisibilityContext);
@@ -21,6 +21,7 @@ import { redo } from '@atlaskit/prosemirror-history/redo';
21
21
  import { undo } from '@atlaskit/prosemirror-history/undo';
22
22
  import { useBlockMenu } from './block-menu-provider';
23
23
  import { BlockMenuRenderer } from './block-menu-renderer/BlockMenuRenderer';
24
+ import { BlockMenuTargetVisibilityProvider } from './block-menu-target-visibility-context';
24
25
  const styles = {
25
26
  base: "_2rko12b0 _bfhk1bhr _16qs130s",
26
27
  maxWidthStyles: "_p12fnklw",
@@ -168,20 +169,23 @@ const BlockMenu = ({
168
169
  scrollableElement
169
170
  }) => {
170
171
  var _openMenuHandleRef$cu, _editorView$dom, _ref2, _api$analytics2;
172
+ const isPopupTargetVisibilityEnabled = isExperimentEnabled('platform_editor_popup_target_visibility');
171
173
  const {
172
174
  menuTriggerBy,
175
+ menuTriggerByNode,
173
176
  isSelectedViaDragHandle,
174
177
  isMenuOpen,
175
178
  currentUserIntent,
176
179
  openedViaKeyboard
177
180
  } = useSharedPluginStateWithSelector(api, ['blockControls', 'userIntent'], states => {
178
- var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$userIntentSta, _states$blockControls4, _states$blockControls5;
181
+ var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$blockControls4, _states$userIntentSta, _states$blockControls5, _states$blockControls6;
179
182
  return {
180
183
  menuTriggerBy: (_states$blockControls = states.blockControlsState) === null || _states$blockControls === void 0 ? void 0 : _states$blockControls.menuTriggerBy,
181
- isSelectedViaDragHandle: (_states$blockControls2 = states.blockControlsState) === null || _states$blockControls2 === void 0 ? void 0 : _states$blockControls2.isSelectedViaDragHandle,
182
- isMenuOpen: (_states$blockControls3 = states.blockControlsState) === null || _states$blockControls3 === void 0 ? void 0 : _states$blockControls3.isMenuOpen,
184
+ menuTriggerByNode: isPopupTargetVisibilityEnabled ? (_states$blockControls2 = states.blockControlsState) === null || _states$blockControls2 === void 0 ? void 0 : _states$blockControls2.menuTriggerByNode : undefined,
185
+ isSelectedViaDragHandle: (_states$blockControls3 = states.blockControlsState) === null || _states$blockControls3 === void 0 ? void 0 : _states$blockControls3.isSelectedViaDragHandle,
186
+ isMenuOpen: (_states$blockControls4 = states.blockControlsState) === null || _states$blockControls4 === void 0 ? void 0 : _states$blockControls4.isMenuOpen,
183
187
  currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent,
184
- openedViaKeyboard: (_states$blockControls4 = states.blockControlsState) === null || _states$blockControls4 === void 0 ? void 0 : (_states$blockControls5 = _states$blockControls4.blockMenuOptions) === null || _states$blockControls5 === void 0 ? void 0 : _states$blockControls5.openedViaKeyboard
188
+ openedViaKeyboard: (_states$blockControls5 = states.blockControlsState) === null || _states$blockControls5 === void 0 ? void 0 : (_states$blockControls6 = _states$blockControls5.blockMenuOptions) === null || _states$blockControls6 === void 0 ? void 0 : _states$blockControls6.openedViaKeyboard
185
189
  };
186
190
  });
187
191
  const {
@@ -213,6 +217,8 @@ const BlockMenu = ({
213
217
  anchoredToRef.current = menuTriggerBy;
214
218
  }
215
219
  const targetHandleRef = isExperimentEnabled('platform_editor_block_control_migration') ? (_openMenuHandleRef$cu = openMenuHandleRef.current) !== null && _openMenuHandleRef$cu !== void 0 ? _openMenuHandleRef$cu : surfaceDragHandle : editorView === null || editorView === void 0 ? void 0 : (_editorView$dom = editorView.dom) === null || _editorView$dom === void 0 ? void 0 : _editorView$dom.querySelector(DRAG_HANDLE_SELECTOR);
220
+ const menuTargetNode = menuTriggerByNode && (editorView === null || editorView === void 0 ? void 0 : editorView.nodeDOM(menuTriggerByNode.pos));
221
+ const visibilityTarget = menuTargetNode instanceof HTMLElement ? menuTargetNode : undefined;
216
222
  const closeMenu = React.useCallback(() => {
217
223
  api === null || api === void 0 ? void 0 : api.core.actions.execute(({
218
224
  tr
@@ -240,19 +246,68 @@ const BlockMenu = ({
240
246
  const prevIsMenuOpenRef = useRef(false);
241
247
  const popupRef = useRef(undefined);
242
248
  const [menuHeight, setMenuHeight] = React.useState(0);
249
+ const [targetVisibility, setTargetVisibility] = React.useState({
250
+ isVisible: undefined,
251
+ restoreFocusTo: null
252
+ });
253
+ const {
254
+ isVisible: targetVisible,
255
+ restoreFocusTo
256
+ } = targetVisibility;
257
+ // CSS visibility can move focus to the document body. Preserve the active menu item so the
258
+ // existing focus-loss guard does not close the menus during a temporary scroll transition.
259
+ const handleTargetVisibilityChanged = React.useCallback(isVisible => {
260
+ var _popupRef$current;
261
+ if (isVisible) {
262
+ setTargetVisibility(current => ({
263
+ isVisible: true,
264
+ restoreFocusTo: current.isVisible === false ? current.restoreFocusTo : null
265
+ }));
266
+ return;
267
+ }
268
+
269
+ // Capture focus before scheduling the state update so the updater stays pure.
270
+ // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
271
+ const activeElement = document.activeElement;
272
+ const isFocusInMenu = activeElement instanceof HTMLElement && (((_popupRef$current = popupRef.current) === null || _popupRef$current === void 0 ? void 0 : _popupRef$current.contains(activeElement)) || activeElement.closest(NESTED_DROPDOWN_MENU) !== null);
273
+ setTargetVisibility({
274
+ isVisible: false,
275
+ restoreFocusTo: isFocusInMenu ? activeElement : null
276
+ });
277
+ }, []);
278
+ React.useLayoutEffect(() => {
279
+ if (!isPopupTargetVisibilityEnabled || !targetVisible || !restoreFocusTo) {
280
+ return;
281
+ }
282
+ const {
283
+ activeElement,
284
+ body,
285
+ documentElement
286
+ } = restoreFocusTo.ownerDocument;
287
+ const focusWasLost = activeElement === null || activeElement === body || activeElement === documentElement;
288
+ if (restoreFocusTo.isConnected && focusWasLost) {
289
+ restoreFocusTo.focus({
290
+ preventScroll: true
291
+ });
292
+ }
293
+ setTargetVisibility(current => ({
294
+ ...current,
295
+ restoreFocusTo: null
296
+ }));
297
+ }, [isPopupTargetVisibilityEnabled, restoreFocusTo, targetVisible]);
243
298
  const targetHandleHeightOffset = -((targetHandleRef === null || targetHandleRef === void 0 ? void 0 : targetHandleRef.clientHeight) || 0);
244
299
  React.useLayoutEffect(() => {
245
- var _popupRef$current;
300
+ var _popupRef$current2;
246
301
  if (!isMenuOpen) {
247
302
  return;
248
303
  }
249
304
  onDropdownOpenChanged(true);
250
- setMenuHeight(((_popupRef$current = popupRef.current) === null || _popupRef$current === void 0 ? void 0 : _popupRef$current.clientHeight) || FALLBACK_MENU_HEIGHT);
305
+ setMenuHeight(((_popupRef$current2 = popupRef.current) === null || _popupRef$current2 === void 0 ? void 0 : _popupRef$current2.clientHeight) || FALLBACK_MENU_HEIGHT);
251
306
  }, [isMenuOpen, onDropdownOpenChanged]);
252
307
 
253
308
  // Nested dropdowns are portaled outside the block-menu popup. Sparse surface reconciliation
254
309
  // dispatches after the menu opens, so a re-render can occur while focus is inside that portal.
255
- const hasFocus = (_ref2 = (editorView === null || editorView === void 0 ? void 0 : editorView.hasFocus()) ||
310
+ const hasFocus = (_ref2 = (editorView === null || editorView === void 0 ? void 0 : editorView.hasFocus()) || isPopupTargetVisibilityEnabled && (targetVisible === false || restoreFocusTo !== null) ||
256
311
  // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
257
312
  document.activeElement === targetHandleRef || popupRef.current && (
258
313
  // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
@@ -323,6 +378,10 @@ const BlockMenu = ({
323
378
  if (!(targetHandleRef instanceof HTMLElement)) {
324
379
  return null;
325
380
  }
381
+ const blockMenuContent = /*#__PURE__*/React.createElement(BlockMenuContent, {
382
+ api: api,
383
+ setRef: setRef
384
+ });
326
385
  return /*#__PURE__*/React.createElement(ErrorBoundary, {
327
386
  component: ACTION_SUBJECT.BLOCK_MENU,
328
387
  dispatchAnalyticsEvent: api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions.fireAnalyticsEvent,
@@ -337,11 +396,14 @@ const BlockMenu = ({
337
396
  boundariesElement: boundariesElement,
338
397
  scrollableElement: scrollableElement,
339
398
  target: targetHandleRef,
399
+ visibilityTarget: visibilityTarget,
340
400
  zIndex: akEditorFloatingOverlapPanelZIndex,
341
401
  fitWidth: DEFAULT_MENU_WIDTH,
342
402
  fitHeight: menuHeight,
343
403
  preventOverflow: true,
344
- stick: true
404
+ stick: true,
405
+ hideWhenTargetOutOfView: isPopupTargetVisibilityEnabled,
406
+ onTargetVisibilityChanged: isPopupTargetVisibilityEnabled ? handleTargetVisibilityChanged : undefined
345
407
  // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
346
408
  ,
347
409
  offset: [DRAG_HANDLE_WIDTH + DRAG_HANDLE_OFFSET_PADDING, targetHandleHeightOffset],
@@ -350,10 +412,9 @@ const BlockMenu = ({
350
412
  {
351
413
  initialFocus: undefined
352
414
  } : undefined
353
- }, /*#__PURE__*/React.createElement(BlockMenuContent, {
354
- api: api,
355
- setRef: setRef
356
- })));
415
+ }, isPopupTargetVisibilityEnabled ? /*#__PURE__*/React.createElement(BlockMenuTargetVisibilityProvider, {
416
+ value: targetVisible
417
+ }, blockMenuContent) : blockMenuContent));
357
418
  };
358
419
 
359
420
  // eslint-disable-next-line @typescript-eslint/no-restricted-types
@@ -6,6 +6,7 @@ import { ToolbarNestedDropdownMenu } from '@atlaskit/editor-toolbar';
6
6
  import ChangesIcon from '@atlaskit/icon/core/changes';
7
7
  import ChevronRightIcon from '@atlaskit/icon/core/chevron-right';
8
8
  import { fg } from '@atlaskit/platform-feature-flags/fg';
9
+ import { useBlockMenuTargetVisibility } from './block-menu-target-visibility-context';
9
10
  import { BLOCK_MENU_ITEM_NAME } from './consts';
10
11
  const BLOCK_MENU_TRANSFORM_SPOTLIGHT_PORTAL_SELECTOR = '[data-test-id="block-menu-transform-spotlight-portal-container"]';
11
12
  const shouldIgnoreBlockMenuTransformSpotlightCloseEvent = event => event.target instanceof Element && event.target.closest(BLOCK_MENU_TRANSFORM_SPOTLIGHT_PORTAL_SELECTOR) !== null;
@@ -16,6 +17,7 @@ export const FormatMenuComponent = ({
16
17
  const {
17
18
  formatMessage
18
19
  } = useIntl();
20
+ const targetVisible = useBlockMenuTargetVisibility();
19
21
  const formatMenuLabel = blockMenuMessages.changeFormat;
20
22
  const handleClick = useCallback(() => {
21
23
  api === null || api === void 0 ? void 0 : api.core.actions.execute(({
@@ -35,6 +37,7 @@ export const FormatMenuComponent = ({
35
37
  });
36
38
  }, [api]);
37
39
  return /*#__PURE__*/React.createElement(ToolbarNestedDropdownMenu, {
40
+ isPopupVisible: targetVisible,
38
41
  text: formatMessage(formatMenuLabel),
39
42
  elemBefore: /*#__PURE__*/React.createElement(ChangesIcon, {
40
43
  label: "",
@@ -1,6 +1,7 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
3
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
4
5
  import { applyTargetTextTypeStep } from './steps/applyTargetTextTypeStep';
5
6
  import { convertEachNodeStep } from './steps/convertEachNodeStep';
6
7
  import { decisionListToListStep } from './steps/decisionListToListStep';
@@ -17,6 +18,19 @@ import { wrapBlockquoteToDecisionListStep } from './steps/wrapBlockquoteToDecisi
17
18
  import { wrapIntoListStep } from './steps/wrapIntoListStep';
18
19
  import { wrapMixedContentStep } from './steps/wrapMixedContentStep';
19
20
  import { wrapStep } from './steps/wrapStep';
21
+ var applyTargetHeadingToParagraphStep = function applyTargetHeadingToParagraphStep(nodes, context) {
22
+ if (!isExperimentEnabled('platform_editor_block_menu_small_text')) {
23
+ return nodes;
24
+ }
25
+ return nodes.map(function (node) {
26
+ var _applyTargetTextTypeS;
27
+ if (node.type.name !== 'heading') {
28
+ return node;
29
+ }
30
+ return (_applyTargetTextTypeS = applyTargetTextTypeStep([node], context).at(0)) !== null && _applyTargetTextTypeS !== void 0 ? _applyTargetTextTypeS : node;
31
+ });
32
+ };
33
+
20
34
  /**
21
35
  * Creates the transformation matrix for all node type pairs.
22
36
  * When includePanelC1 is true (platform_editor_nest_table_in_panel experiment on),
@@ -64,7 +78,7 @@ var createTransformationMatrix = function createTransformationMatrix(includePane
64
78
  expand: [unwrapStep, wrapStep],
65
79
  nestedExpand: [unwrapStep, wrapStep],
66
80
  layoutSection: [unwrapStep, wrapMixedContentStep],
67
- paragraph: [unwrapStep]
81
+ paragraph: [unwrapStep, applyTargetHeadingToParagraphStep]
68
82
  },
69
83
  panel_c1: includePanelC1 ? {
70
84
  blockquote: [unwrapStep, wrapMixedContentStep],
@@ -72,7 +86,7 @@ var createTransformationMatrix = function createTransformationMatrix(includePane
72
86
  expand: [unwrapStep, wrapStep],
73
87
  nestedExpand: [unwrapStep, wrapStep],
74
88
  layoutSection: [unwrapStep, wrapMixedContentStep],
75
- paragraph: [unwrapStep]
89
+ paragraph: [unwrapStep, applyTargetHeadingToParagraphStep]
76
90
  } : {},
77
91
  expand: _objectSpread(_objectSpread({
78
92
  panel: [unwrapExpandStep, wrapMixedContentStep]
@@ -82,7 +96,7 @@ var createTransformationMatrix = function createTransformationMatrix(includePane
82
96
  blockquote: [unwrapExpandStep, wrapMixedContentStep],
83
97
  layoutSection: [unwrapExpandStep, wrapMixedContentStep],
84
98
  nestedExpand: [unwrapExpandStep, wrapStep],
85
- paragraph: [unwrapExpandStep]
99
+ paragraph: [unwrapExpandStep, applyTargetHeadingToParagraphStep]
86
100
  }),
87
101
  nestedExpand: _objectSpread(_objectSpread({
88
102
  panel: [unwrapExpandStep, wrapMixedContentStep]
@@ -91,7 +105,7 @@ var createTransformationMatrix = function createTransformationMatrix(includePane
91
105
  } : {}), {}, {
92
106
  blockquote: [unwrapExpandStep, wrapMixedContentStep],
93
107
  layoutSection: [unwrapExpandStep, wrapMixedContentStep],
94
- paragraph: [unwrapExpandStep]
108
+ paragraph: [unwrapExpandStep, applyTargetHeadingToParagraphStep]
95
109
  }),
96
110
  blockquote: _objectSpread(_objectSpread({
97
111
  expand: [wrapStep],
@@ -101,7 +115,7 @@ var createTransformationMatrix = function createTransformationMatrix(includePane
101
115
  }, includePanelC1 ? {
102
116
  panel_c1: [unwrapStep, wrapStep]
103
117
  } : {}), {}, {
104
- paragraph: [unwrapStep],
118
+ paragraph: [unwrapStep, applyTargetHeadingToParagraphStep],
105
119
  decisionList: [unwrapStep, wrapBlockquoteToDecisionListStep]
106
120
  }),
107
121
  layoutSection: _objectSpread(_objectSpread({
@@ -112,7 +126,7 @@ var createTransformationMatrix = function createTransformationMatrix(includePane
112
126
  }, includePanelC1 ? {
113
127
  panel_c1: [unwrapLayoutStep, wrapMixedContentStep]
114
128
  } : {}), {}, {
115
- paragraph: [unwrapLayoutStep]
129
+ paragraph: [unwrapLayoutStep, applyTargetHeadingToParagraphStep]
116
130
  }),
117
131
  codeBlock: _objectSpread(_objectSpread({
118
132
  blockquote: [wrapStep],
@@ -6,7 +6,14 @@ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
6
6
  import { getTargetNodeTypeNameInContext } from '../transform-node-utils/utils';
7
7
  import { TRANSFORMATION_MATRIX, TRANSFORMATION_MATRIX_PANEL_C1 } from './TRANSFORMATION_MATRIX';
8
8
  import { getNodeName, toNodeTypeValue } from './types';
9
+ var LIST_NODE_TYPE_NAMES = new Set(['bulletList', 'orderedList', 'taskList', 'decisionList', 'listItem', 'taskItem', 'blockTaskItem', 'decisionItem']);
10
+ var shouldRecurseThroughListNode = function shouldRecurseThroughListNode(node) {
11
+ return LIST_NODE_TYPE_NAMES.has(node.type.name);
12
+ };
9
13
  var _applyTargetNodeMarks = function applyTargetNodeMarks(node, targetNodeType, marksToAdd, marksToRemove, schema) {
14
+ var shouldRecurseIntoChildren = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : function () {
15
+ return true;
16
+ };
10
17
  var nextNode = node;
11
18
  if (node.type === targetNodeType) {
12
19
  var marksAfterRemovals = (marksToRemove !== null && marksToRemove !== void 0 ? marksToRemove : []).reduce(function (currentMarks, name) {
@@ -22,12 +29,12 @@ var _applyTargetNodeMarks = function applyTargetNodeMarks(node, targetNodeType,
22
29
  }, marksAfterRemovals);
23
30
  nextNode = node.mark(marks);
24
31
  }
25
- if (nextNode.childCount === 0) {
32
+ if (!shouldRecurseIntoChildren(nextNode) || nextNode.childCount === 0) {
26
33
  return nextNode;
27
34
  }
28
35
  var children = [];
29
36
  nextNode.forEach(function (child) {
30
- return children.push(_applyTargetNodeMarks(child, targetNodeType, marksToAdd, marksToRemove, schema));
37
+ return children.push(_applyTargetNodeMarks(child, targetNodeType, marksToAdd, marksToRemove, schema, shouldRecurseIntoChildren));
31
38
  });
32
39
  return nextNode.copy(Fragment.fromArray(children));
33
40
  };
@@ -105,12 +112,13 @@ export var convertNodesToTargetType = function convertNodesToTargetType(_ref3) {
105
112
  }, sourceNodes);
106
113
  return upgradePanelNodesToPanelC1(_resultNodes, parentNode, schema);
107
114
  }
115
+ var shouldLimitTargetNodeMarkRecursion = targetNodeTypeName === 'paragraph' && ['multi', 'panel', 'panel_c1', 'expand', 'nestedExpand', 'blockquote', 'layoutSection'].includes(selectedNodeTypeName);
108
116
  var resultNodes = (_steps$reduce = steps === null || steps === void 0 ? void 0 : steps.reduce(function (nodes, step) {
109
117
  return step(nodes, context);
110
118
  }, sourceNodes)) !== null && _steps$reduce !== void 0 ? _steps$reduce : sourceNodes;
111
119
  var upgradedNodes = steps !== null && steps !== void 0 && steps.length ? upgradePanelNodesToPanelC1(resultNodes, parentNode, schema) : resultNodes;
112
120
  return upgradedNodes.map(function (node) {
113
- return _applyTargetNodeMarks(node, targetNodeType, marksToAdd, marksToRemove, schema);
121
+ return _applyTargetNodeMarks(node, targetNodeType, marksToAdd, marksToRemove, schema, shouldLimitTargetNodeMarkRecursion ? shouldRecurseThroughListNode : undefined);
114
122
  });
115
123
  };
116
124
  export var isTransformDisabledBasedOnStepsConfig = function isTransformDisabledBasedOnStepsConfig(selectedNodeType, targetNodeType) {
@@ -3,11 +3,14 @@ import { useIntl } from 'react-intl';
3
3
  import { blockMenuMessages } from '@atlaskit/editor-common/messages';
4
4
  import { ToolbarDropdownItem, ToolbarDropdownItemSection, ToolbarNestedDropdownMenu } from '@atlaskit/editor-toolbar';
5
5
  import ChevronRightIcon from '@atlaskit/icon/core/chevron-right';
6
+ import { useBlockMenuTargetVisibility } from '../block-menu-target-visibility-context';
6
7
  var FallbackNestedMenu = function FallbackNestedMenu(_ref) {
7
8
  var children = _ref.children;
8
9
  var _useIntl = useIntl(),
9
10
  formatMessage = _useIntl.formatMessage;
11
+ var targetVisible = useBlockMenuTargetVisibility();
10
12
  return /*#__PURE__*/React.createElement(ToolbarNestedDropdownMenu, {
13
+ isPopupVisible: targetVisible,
11
14
  elemBefore: undefined,
12
15
  elemAfter: /*#__PURE__*/React.createElement(ChevronRightIcon, {
13
16
  label: "",
@@ -0,0 +1,6 @@
1
+ import { createContext, useContext } from 'react';
2
+ var BlockMenuTargetVisibilityContext = /*#__PURE__*/createContext(undefined);
3
+ export var BlockMenuTargetVisibilityProvider = BlockMenuTargetVisibilityContext.Provider;
4
+ export var useBlockMenuTargetVisibility = function useBlockMenuTargetVisibility() {
5
+ return useContext(BlockMenuTargetVisibilityContext);
6
+ };
@@ -1,7 +1,10 @@
1
1
  /* block-menu.tsx generated by @compiled/babel-plugin v3.0.2 */
2
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
3
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
4
  import "./block-menu.compiled.css";
4
5
  import { ax, ix } from "@compiled/react/runtime";
6
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
7
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5
8
  import React, { useContext, useEffect, useRef } from 'react';
6
9
  import { injectIntl } from 'react-intl';
7
10
  import { cx } from '@atlaskit/css';
@@ -22,6 +25,7 @@ import { redo } from '@atlaskit/prosemirror-history/redo';
22
25
  import { undo } from '@atlaskit/prosemirror-history/undo';
23
26
  import { useBlockMenu } from './block-menu-provider';
24
27
  import { BlockMenuRenderer } from './block-menu-renderer/BlockMenuRenderer';
28
+ import { BlockMenuTargetVisibilityProvider } from './block-menu-target-visibility-context';
25
29
  var styles = {
26
30
  base: "_2rko12b0 _bfhk1bhr _16qs130s",
27
31
  maxWidthStyles: "_p12fnklw",
@@ -171,17 +175,20 @@ var BlockMenu = function BlockMenu(_ref5) {
171
175
  mountTo = _ref5.mountTo,
172
176
  boundariesElement = _ref5.boundariesElement,
173
177
  scrollableElement = _ref5.scrollableElement;
178
+ var isPopupTargetVisibilityEnabled = isExperimentEnabled('platform_editor_popup_target_visibility');
174
179
  var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['blockControls', 'userIntent'], function (states) {
175
- var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$userIntentSta, _states$blockControls4;
180
+ var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$blockControls4, _states$userIntentSta, _states$blockControls5;
176
181
  return {
177
182
  menuTriggerBy: (_states$blockControls = states.blockControlsState) === null || _states$blockControls === void 0 ? void 0 : _states$blockControls.menuTriggerBy,
178
- isSelectedViaDragHandle: (_states$blockControls2 = states.blockControlsState) === null || _states$blockControls2 === void 0 ? void 0 : _states$blockControls2.isSelectedViaDragHandle,
179
- isMenuOpen: (_states$blockControls3 = states.blockControlsState) === null || _states$blockControls3 === void 0 ? void 0 : _states$blockControls3.isMenuOpen,
183
+ menuTriggerByNode: isPopupTargetVisibilityEnabled ? (_states$blockControls2 = states.blockControlsState) === null || _states$blockControls2 === void 0 ? void 0 : _states$blockControls2.menuTriggerByNode : undefined,
184
+ isSelectedViaDragHandle: (_states$blockControls3 = states.blockControlsState) === null || _states$blockControls3 === void 0 ? void 0 : _states$blockControls3.isSelectedViaDragHandle,
185
+ isMenuOpen: (_states$blockControls4 = states.blockControlsState) === null || _states$blockControls4 === void 0 ? void 0 : _states$blockControls4.isMenuOpen,
180
186
  currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent,
181
- openedViaKeyboard: (_states$blockControls4 = states.blockControlsState) === null || _states$blockControls4 === void 0 || (_states$blockControls4 = _states$blockControls4.blockMenuOptions) === null || _states$blockControls4 === void 0 ? void 0 : _states$blockControls4.openedViaKeyboard
187
+ openedViaKeyboard: (_states$blockControls5 = states.blockControlsState) === null || _states$blockControls5 === void 0 || (_states$blockControls5 = _states$blockControls5.blockMenuOptions) === null || _states$blockControls5 === void 0 ? void 0 : _states$blockControls5.openedViaKeyboard
182
188
  };
183
189
  }),
184
190
  menuTriggerBy = _useSharedPluginState.menuTriggerBy,
191
+ menuTriggerByNode = _useSharedPluginState.menuTriggerByNode,
185
192
  isSelectedViaDragHandle = _useSharedPluginState.isSelectedViaDragHandle,
186
193
  isMenuOpen = _useSharedPluginState.isMenuOpen,
187
194
  currentUserIntent = _useSharedPluginState.currentUserIntent,
@@ -219,6 +226,8 @@ var BlockMenu = function BlockMenu(_ref5) {
219
226
  anchoredToRef.current = menuTriggerBy;
220
227
  }
221
228
  var targetHandleRef = isExperimentEnabled('platform_editor_block_control_migration') ? (_openMenuHandleRef$cu = openMenuHandleRef.current) !== null && _openMenuHandleRef$cu !== void 0 ? _openMenuHandleRef$cu : surfaceDragHandle : editorView === null || editorView === void 0 || (_editorView$dom = editorView.dom) === null || _editorView$dom === void 0 ? void 0 : _editorView$dom.querySelector(DRAG_HANDLE_SELECTOR);
229
+ var menuTargetNode = menuTriggerByNode && (editorView === null || editorView === void 0 ? void 0 : editorView.nodeDOM(menuTriggerByNode.pos));
230
+ var visibilityTarget = menuTargetNode instanceof HTMLElement ? menuTargetNode : undefined;
222
231
  var closeMenu = React.useCallback(function () {
223
232
  api === null || api === void 0 || api.core.actions.execute(function (_ref7) {
224
233
  var _api$blockControls, _api$userIntent2;
@@ -248,19 +257,71 @@ var BlockMenu = function BlockMenu(_ref5) {
248
257
  _React$useState4 = _slicedToArray(_React$useState3, 2),
249
258
  menuHeight = _React$useState4[0],
250
259
  setMenuHeight = _React$useState4[1];
260
+ var _React$useState5 = React.useState({
261
+ isVisible: undefined,
262
+ restoreFocusTo: null
263
+ }),
264
+ _React$useState6 = _slicedToArray(_React$useState5, 2),
265
+ targetVisibility = _React$useState6[0],
266
+ setTargetVisibility = _React$useState6[1];
267
+ var targetVisible = targetVisibility.isVisible,
268
+ restoreFocusTo = targetVisibility.restoreFocusTo;
269
+ // CSS visibility can move focus to the document body. Preserve the active menu item so the
270
+ // existing focus-loss guard does not close the menus during a temporary scroll transition.
271
+ var handleTargetVisibilityChanged = React.useCallback(function (isVisible) {
272
+ var _popupRef$current;
273
+ if (isVisible) {
274
+ setTargetVisibility(function (current) {
275
+ return {
276
+ isVisible: true,
277
+ restoreFocusTo: current.isVisible === false ? current.restoreFocusTo : null
278
+ };
279
+ });
280
+ return;
281
+ }
282
+
283
+ // Capture focus before scheduling the state update so the updater stays pure.
284
+ // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
285
+ var activeElement = document.activeElement;
286
+ var isFocusInMenu = activeElement instanceof HTMLElement && (((_popupRef$current = popupRef.current) === null || _popupRef$current === void 0 ? void 0 : _popupRef$current.contains(activeElement)) || activeElement.closest(NESTED_DROPDOWN_MENU) !== null);
287
+ setTargetVisibility({
288
+ isVisible: false,
289
+ restoreFocusTo: isFocusInMenu ? activeElement : null
290
+ });
291
+ }, []);
292
+ React.useLayoutEffect(function () {
293
+ if (!isPopupTargetVisibilityEnabled || !targetVisible || !restoreFocusTo) {
294
+ return;
295
+ }
296
+ var _restoreFocusTo$owner = restoreFocusTo.ownerDocument,
297
+ activeElement = _restoreFocusTo$owner.activeElement,
298
+ body = _restoreFocusTo$owner.body,
299
+ documentElement = _restoreFocusTo$owner.documentElement;
300
+ var focusWasLost = activeElement === null || activeElement === body || activeElement === documentElement;
301
+ if (restoreFocusTo.isConnected && focusWasLost) {
302
+ restoreFocusTo.focus({
303
+ preventScroll: true
304
+ });
305
+ }
306
+ setTargetVisibility(function (current) {
307
+ return _objectSpread(_objectSpread({}, current), {}, {
308
+ restoreFocusTo: null
309
+ });
310
+ });
311
+ }, [isPopupTargetVisibilityEnabled, restoreFocusTo, targetVisible]);
251
312
  var targetHandleHeightOffset = -((targetHandleRef === null || targetHandleRef === void 0 ? void 0 : targetHandleRef.clientHeight) || 0);
252
313
  React.useLayoutEffect(function () {
253
- var _popupRef$current;
314
+ var _popupRef$current2;
254
315
  if (!isMenuOpen) {
255
316
  return;
256
317
  }
257
318
  onDropdownOpenChanged(true);
258
- setMenuHeight(((_popupRef$current = popupRef.current) === null || _popupRef$current === void 0 ? void 0 : _popupRef$current.clientHeight) || FALLBACK_MENU_HEIGHT);
319
+ setMenuHeight(((_popupRef$current2 = popupRef.current) === null || _popupRef$current2 === void 0 ? void 0 : _popupRef$current2.clientHeight) || FALLBACK_MENU_HEIGHT);
259
320
  }, [isMenuOpen, onDropdownOpenChanged]);
260
321
 
261
322
  // Nested dropdowns are portaled outside the block-menu popup. Sparse surface reconciliation
262
323
  // dispatches after the menu opens, so a re-render can occur while focus is inside that portal.
263
- var hasFocus = (_ref8 = (editorView === null || editorView === void 0 ? void 0 : editorView.hasFocus()) ||
324
+ var hasFocus = (_ref8 = (editorView === null || editorView === void 0 ? void 0 : editorView.hasFocus()) || isPopupTargetVisibilityEnabled && (targetVisible === false || restoreFocusTo !== null) ||
264
325
  // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
265
326
  document.activeElement === targetHandleRef || popupRef.current && (
266
327
  // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
@@ -331,6 +392,10 @@ var BlockMenu = function BlockMenu(_ref5) {
331
392
  if (!(targetHandleRef instanceof HTMLElement)) {
332
393
  return null;
333
394
  }
395
+ var blockMenuContent = /*#__PURE__*/React.createElement(BlockMenuContent, {
396
+ api: api,
397
+ setRef: setRef
398
+ });
334
399
  return /*#__PURE__*/React.createElement(ErrorBoundary, {
335
400
  component: ACTION_SUBJECT.BLOCK_MENU,
336
401
  dispatchAnalyticsEvent: api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions.fireAnalyticsEvent,
@@ -345,11 +410,14 @@ var BlockMenu = function BlockMenu(_ref5) {
345
410
  boundariesElement: boundariesElement,
346
411
  scrollableElement: scrollableElement,
347
412
  target: targetHandleRef,
413
+ visibilityTarget: visibilityTarget,
348
414
  zIndex: akEditorFloatingOverlapPanelZIndex,
349
415
  fitWidth: DEFAULT_MENU_WIDTH,
350
416
  fitHeight: menuHeight,
351
417
  preventOverflow: true,
352
- stick: true
418
+ stick: true,
419
+ hideWhenTargetOutOfView: isPopupTargetVisibilityEnabled,
420
+ onTargetVisibilityChanged: isPopupTargetVisibilityEnabled ? handleTargetVisibilityChanged : undefined
353
421
  // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
354
422
  ,
355
423
  offset: [DRAG_HANDLE_WIDTH + DRAG_HANDLE_OFFSET_PADDING, targetHandleHeightOffset],
@@ -358,10 +426,9 @@ var BlockMenu = function BlockMenu(_ref5) {
358
426
  {
359
427
  initialFocus: undefined
360
428
  } : undefined
361
- }, /*#__PURE__*/React.createElement(BlockMenuContent, {
362
- api: api,
363
- setRef: setRef
364
- })));
429
+ }, isPopupTargetVisibilityEnabled ? /*#__PURE__*/React.createElement(BlockMenuTargetVisibilityProvider, {
430
+ value: targetVisible
431
+ }, blockMenuContent) : blockMenuContent));
365
432
  };
366
433
 
367
434
  // eslint-disable-next-line @typescript-eslint/no-restricted-types
@@ -6,6 +6,7 @@ import { ToolbarNestedDropdownMenu } from '@atlaskit/editor-toolbar';
6
6
  import ChangesIcon from '@atlaskit/icon/core/changes';
7
7
  import ChevronRightIcon from '@atlaskit/icon/core/chevron-right';
8
8
  import { fg } from '@atlaskit/platform-feature-flags/fg';
9
+ import { useBlockMenuTargetVisibility } from './block-menu-target-visibility-context';
9
10
  import { BLOCK_MENU_ITEM_NAME } from './consts';
10
11
  var BLOCK_MENU_TRANSFORM_SPOTLIGHT_PORTAL_SELECTOR = '[data-test-id="block-menu-transform-spotlight-portal-container"]';
11
12
  var shouldIgnoreBlockMenuTransformSpotlightCloseEvent = function shouldIgnoreBlockMenuTransformSpotlightCloseEvent(event) {
@@ -16,6 +17,7 @@ export var FormatMenuComponent = function FormatMenuComponent(_ref) {
16
17
  children = _ref.children;
17
18
  var _useIntl = useIntl(),
18
19
  formatMessage = _useIntl.formatMessage;
20
+ var targetVisible = useBlockMenuTargetVisibility();
19
21
  var formatMenuLabel = blockMenuMessages.changeFormat;
20
22
  var handleClick = useCallback(function () {
21
23
  api === null || api === void 0 || api.core.actions.execute(function (_ref2) {
@@ -34,6 +36,7 @@ export var FormatMenuComponent = function FormatMenuComponent(_ref) {
34
36
  });
35
37
  }, [api]);
36
38
  return /*#__PURE__*/React.createElement(ToolbarNestedDropdownMenu, {
39
+ isPopupVisible: targetVisible,
37
40
  text: formatMessage(formatMenuLabel),
38
41
  elemBefore: /*#__PURE__*/React.createElement(ChangesIcon, {
39
42
  label: "",
@@ -0,0 +1,3 @@
1
+ import { type Provider } from 'react';
2
+ export declare const BlockMenuTargetVisibilityProvider: Provider<boolean | undefined>;
3
+ export declare const useBlockMenuTargetVisibility: () => boolean | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-block-menu",
3
- "version": "20.0.3",
3
+ "version": "20.0.5",
4
4
  "description": "BlockMenu plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -35,21 +35,21 @@
35
35
  "@atlaskit/editor-prosemirror": "^9.0.0",
36
36
  "@atlaskit/editor-shared-styles": "^4.3.0",
37
37
  "@atlaskit/editor-tables": "^3.2.0",
38
- "@atlaskit/editor-toolbar": "^2.7.0",
38
+ "@atlaskit/editor-toolbar": "^2.8.0",
39
39
  "@atlaskit/flag": "^19.0.0",
40
40
  "@atlaskit/icon": "^38.0.0",
41
41
  "@atlaskit/platform-feature-experiments": "^0.3.0",
42
42
  "@atlaskit/platform-feature-flags": "^2.2.0",
43
43
  "@atlaskit/primitives": "^22.5.0",
44
44
  "@atlaskit/prosemirror-history": "^1.2.0",
45
- "@atlaskit/tmp-editor-statsig": "^208.0.0",
45
+ "@atlaskit/tmp-editor-statsig": "^209.0.0",
46
46
  "@atlaskit/tokens": "^20.0.0",
47
47
  "@babel/runtime": "^7.0.0",
48
48
  "bind-event-listener": "^3.0.0",
49
49
  "memoize-one": "^6.0.0"
50
50
  },
51
51
  "peerDependencies": {
52
- "@atlaskit/editor-common": "^125.2.0",
52
+ "@atlaskit/editor-common": "^125.4.0",
53
53
  "react": "^18.2.0 || ^19.2.0",
54
54
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
55
55
  },