@atlaskit/editor-plugin-block-menu 4.0.3 → 4.0.4

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,16 @@
1
1
  # @atlaskit/editor-plugin-block-menu
2
2
 
3
+ ## 4.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [`1eda79686167c`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/1eda79686167c) -
8
+ ED-29418: Fix empty code block convert to lists
9
+ - [`0778701e62192`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/0778701e62192) -
10
+ [ux] ED-29424 Focus first menu item when block menu is opened and remove decorations when delete
11
+ button unmounts
12
+ - Updated dependencies
13
+
3
14
  ## 4.0.3
4
15
 
5
16
  ### Patch Changes
@@ -8,6 +8,7 @@ exports.unwrapAndConvertToList = exports.unwrapAndConvertToBlockType = exports.t
8
8
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
9
  var _model = require("@atlaskit/editor-prosemirror/model");
10
10
  var _utils = require("@atlaskit/editor-prosemirror/utils");
11
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
11
12
  var _inlineNodeTransforms = require("./inline-node-transforms");
12
13
  var _utils2 = require("./utils");
13
14
  var convertInvalidNodeToValidNodeType = function convertInvalidNodeToValidNodeType(sourceContent, sourceNodeType, validNodeType, withMarks) {
@@ -203,7 +204,11 @@ var unwrapAndConvertToList = exports.unwrapAndConvertToList = function unwrapAnd
203
204
  heading = _schema$nodes2.heading;
204
205
  var isTargetTaskList = targetNodeType === taskList;
205
206
  var createListItemFromInline = function createListItemFromInline(content) {
206
- return isTargetTaskList ? taskItem.create(null, content) : listItem.create(null, paragraph.create(null, content));
207
+ if (!content && (0, _platformFeatureFlags.fg)('platform_editor_block_menu_patch_2')) {
208
+ return isTargetTaskList ? taskItem.create() : listItem.create(null, paragraph.create());
209
+ } else {
210
+ return isTargetTaskList ? taskItem.create(null, content) : listItem.create(null, paragraph.create(null, content));
211
+ }
207
212
  };
208
213
  var getInlineContent = function getInlineContent(textblock) {
209
214
  var inlineContent = [];
@@ -233,6 +238,15 @@ var unwrapAndConvertToList = exports.unwrapAndConvertToList = function unwrapAnd
233
238
  };
234
239
  if (sourceNode.type.name === 'codeBlock') {
235
240
  var codeText = sourceNode.textContent;
241
+ // check if code block only contains newline characters
242
+ // eslint-disable-next-line require-unicode-regexp
243
+ var isOnlyNewLines = function isOnlyNewLines(codeText) {
244
+ return codeText.replace(/\n/g, '').trim() === '';
245
+ };
246
+ if ((!codeText || isOnlyNewLines(codeText)) && (0, _platformFeatureFlags.fg)('platform_editor_block_menu_patch_2')) {
247
+ // Empty code block - create an empty list item
248
+ currentListItems.push(createListItemFromInline());
249
+ }
236
250
  if (codeText) {
237
251
  var lines = codeText.split('\n');
238
252
  // Remove empty lines
@@ -9,6 +9,7 @@ exports.BlockMenuRenderer = void 0;
9
9
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
10
10
  var _react = _interopRequireWildcard(require("react"));
11
11
  var _uiMenu = require("@atlaskit/editor-common/ui-menu");
12
+ var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
12
13
  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); }
13
14
  var NoOp = function NoOp(props) {
14
15
  return null;
@@ -50,48 +51,51 @@ var BlockMenuRenderer = exports.BlockMenuRenderer = function BlockMenuRenderer(_
50
51
  var menuSections = getSortedNonNestedSections(components);
51
52
  var menuItems = components.filter(isMenuItem);
52
53
  var nestedMenus = components.filter(isNestedMenu);
53
- return /*#__PURE__*/_react.default.createElement(_uiMenu.ArrowKeyNavigationProvider, {
54
- type: _uiMenu.ArrowKeyNavigationType.MENU
55
- }, /*#__PURE__*/_react.default.createElement(_react.Fragment, null, menuSections.map(function (section) {
56
- // Get all items for the current section, including nested menus, and sort them by rank
57
- var currentSectionItemsSorted = getSortedChildren([].concat((0, _toConsumableArray2.default)(menuItems), (0, _toConsumableArray2.default)(nestedMenus)), section.key);
58
- if (currentSectionItemsSorted.length === 0) {
59
- return null;
60
- }
54
+ var renderMenu = function renderMenu() {
55
+ return /*#__PURE__*/_react.default.createElement(_react.Fragment, null, menuSections.map(function (section) {
56
+ // Get all items for the current section, including nested menus, and sort them by rank
57
+ var currentSectionItemsSorted = getSortedChildren([].concat((0, _toConsumableArray2.default)(menuItems), (0, _toConsumableArray2.default)(nestedMenus)), section.key);
58
+ if (currentSectionItemsSorted.length === 0) {
59
+ return null;
60
+ }
61
61
 
62
- // iterate over the current section items, if it is nested menu, get their children, sort them
63
- // if they are menu items, just render as they are sorted above
64
- var getChildrenWithNestedItems = function getChildrenWithNestedItems(items) {
65
- return items.map(function (item) {
66
- if (isNestedMenu(item)) {
67
- var sortedNestedSections = getSortedNestedSections(components, item.key);
68
- return sortedNestedSections.map(function (section) {
69
- var sortedNestedMenuItems = getSortedChildren(menuItems, section.key);
70
- var NestedMenuComponent = item.component || fallbacks.nestedMenu || NoOp;
71
- var NestedSection = section.component || fallbacks.section || NoOp;
72
- return /*#__PURE__*/_react.default.createElement(NestedMenuComponent, {
62
+ // iterate over the current section items, if it is nested menu, get their children, sort them
63
+ // if they are menu items, just render as they are sorted above
64
+ var getChildrenWithNestedItems = function getChildrenWithNestedItems(items) {
65
+ return items.map(function (item) {
66
+ if (isNestedMenu(item)) {
67
+ var sortedNestedSections = getSortedNestedSections(components, item.key);
68
+ return sortedNestedSections.map(function (section) {
69
+ var sortedNestedMenuItems = getSortedChildren(menuItems, section.key);
70
+ var NestedMenuComponent = item.component || fallbacks.nestedMenu || NoOp;
71
+ var NestedSection = section.component || fallbacks.section || NoOp;
72
+ return /*#__PURE__*/_react.default.createElement(NestedMenuComponent, {
73
+ key: item.key
74
+ }, /*#__PURE__*/_react.default.createElement(NestedSection, {
75
+ key: section.key
76
+ }, sortedNestedMenuItems.map(function (nestedItem) {
77
+ var NestedMenuItemComponent = nestedItem.component || fallbacks.item || NoOp;
78
+ return /*#__PURE__*/_react.default.createElement(NestedMenuItemComponent, {
79
+ key: nestedItem.key
80
+ });
81
+ })));
82
+ });
83
+ } else {
84
+ var ItemComponent = item.component || fallbacks.item || NoOp;
85
+ return /*#__PURE__*/_react.default.createElement(ItemComponent, {
73
86
  key: item.key
74
- }, /*#__PURE__*/_react.default.createElement(NestedSection, {
75
- key: section.key
76
- }, sortedNestedMenuItems.map(function (nestedItem) {
77
- var NestedMenuItemComponent = nestedItem.component || fallbacks.item || NoOp;
78
- return /*#__PURE__*/_react.default.createElement(NestedMenuItemComponent, {
79
- key: nestedItem.key
80
- });
81
- })));
82
- });
83
- } else {
84
- var ItemComponent = item.component || fallbacks.item || NoOp;
85
- return /*#__PURE__*/_react.default.createElement(ItemComponent, {
86
- key: item.key
87
- });
88
- }
89
- });
90
- };
91
- var children = getChildrenWithNestedItems(currentSectionItemsSorted);
92
- var SectionComponent = section.component || fallbacks.section || NoOp;
93
- return /*#__PURE__*/_react.default.createElement(SectionComponent, {
94
- key: section.key
95
- }, children);
96
- })));
87
+ });
88
+ }
89
+ });
90
+ };
91
+ var children = getChildrenWithNestedItems(currentSectionItemsSorted);
92
+ var SectionComponent = section.component || fallbacks.section || NoOp;
93
+ return /*#__PURE__*/_react.default.createElement(SectionComponent, {
94
+ key: section.key
95
+ }, children);
96
+ }));
97
+ };
98
+ return (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true) ? /*#__PURE__*/_react.default.createElement(_uiMenu.ArrowKeyNavigationProvider, {
99
+ type: _uiMenu.ArrowKeyNavigationType.MENU
100
+ }, renderMenu()) : renderMenu();
97
101
  };
@@ -64,18 +64,20 @@ var BlockMenu = function BlockMenu(_ref2) {
64
64
  boundariesElement = _ref2.boundariesElement,
65
65
  scrollableElement = _ref2.scrollableElement;
66
66
  var _useSharedPluginState = (0, _hooks.useSharedPluginStateWithSelector)(api, ['blockControls', 'userIntent'], function (states) {
67
- var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$userIntentSta;
67
+ var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$userIntentSta, _states$blockControls4;
68
68
  return {
69
69
  menuTriggerBy: (_states$blockControls = states.blockControlsState) === null || _states$blockControls === void 0 ? void 0 : _states$blockControls.menuTriggerBy,
70
70
  isSelectedViaDragHandle: (_states$blockControls2 = states.blockControlsState) === null || _states$blockControls2 === void 0 ? void 0 : _states$blockControls2.isSelectedViaDragHandle,
71
71
  isMenuOpen: (_states$blockControls3 = states.blockControlsState) === null || _states$blockControls3 === void 0 ? void 0 : _states$blockControls3.isMenuOpen,
72
- currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent
72
+ currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent,
73
+ 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
73
74
  };
74
75
  }),
75
76
  menuTriggerBy = _useSharedPluginState.menuTriggerBy,
76
77
  isSelectedViaDragHandle = _useSharedPluginState.isSelectedViaDragHandle,
77
78
  isMenuOpen = _useSharedPluginState.isMenuOpen,
78
- currentUserIntent = _useSharedPluginState.currentUserIntent;
79
+ currentUserIntent = _useSharedPluginState.currentUserIntent,
80
+ openedViaKeyboard = _useSharedPluginState.openedViaKeyboard;
79
81
  var _useBlockMenu = (0, _blockMenuProvider.useBlockMenu)(),
80
82
  onDropdownOpenChanged = _useBlockMenu.onDropdownOpenChanged,
81
83
  fireAnalyticsEvent = _useBlockMenu.fireAnalyticsEvent;
@@ -86,7 +88,7 @@ var BlockMenu = function BlockMenu(_ref2) {
86
88
  // hasSelection true, always show block menu
87
89
  // hasSelection false, only show block menu when empty line experiment is enabled
88
90
  var shouldShowBlockMenuForEmptyLine = hasSelection || !hasSelection && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_empty_line', 'isEnabled', true);
89
- var selectedByShortcutORDragHandle = isSelectedViaDragHandle;
91
+ var selectedByShortcutORDragHandle = isSelectedViaDragHandle || openedViaKeyboard && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true);
90
92
  (0, _react.useEffect)(function () {
91
93
  var _api$userIntent;
92
94
  if (!isMenuOpen || !menuTriggerBy || !selectedByShortcutORDragHandle || !hasFocus || !shouldShowBlockMenuForEmptyLine || ['resizing', 'dragging'].includes(currentUserIntent || '')) {
@@ -149,7 +151,9 @@ var BlockMenu = function BlockMenu(_ref2) {
149
151
  preventOverflow: true // disables forced horizontal placement when forcePlacement is on, so fitWidth controls flipping
150
152
  ,
151
153
  stick: true,
152
- focusTrap: true,
154
+ focusTrap: (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true) ? openedViaKeyboard ? {
155
+ initialFocus: undefined
156
+ } : true : undefined,
153
157
  offset: [_styles.DRAG_HANDLE_WIDTH + DRAG_HANDLE_OFFSET_PADDING, 0]
154
158
  }, /*#__PURE__*/_react.default.createElement(BlockMenuContent, {
155
159
  api: api
@@ -78,10 +78,22 @@ var DeleteDropdownItemContent = function DeleteDropdownItemContent(_ref) {
78
78
  return tr;
79
79
  });
80
80
  }, [api, nodeTypes]);
81
- var onRemoveHoverDecoration = function onRemoveHoverDecoration() {
81
+ var onRemoveHoverDecoration = (0, _react.useCallback)(function () {
82
82
  var _api$decorations2, _api$decorations2$rem;
83
83
  api === null || api === void 0 || api.core.actions.execute(api === null || api === void 0 || (_api$decorations2 = api.decorations) === null || _api$decorations2 === void 0 || (_api$decorations2 = _api$decorations2.commands) === null || _api$decorations2 === void 0 || (_api$decorations2$rem = _api$decorations2.removeDecoration) === null || _api$decorations2$rem === void 0 ? void 0 : _api$decorations2$rem.call(_api$decorations2));
84
- };
84
+ }, [api]);
85
+ (0, _react.useEffect)(function () {
86
+ if (!(0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true)) {
87
+ return;
88
+ }
89
+ return function () {
90
+ if (!(0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true)) {
91
+ return;
92
+ }
93
+ // clean up hover decoration when unmounting
94
+ onRemoveHoverDecoration();
95
+ };
96
+ }, [onRemoveHoverDecoration]);
85
97
  var text = (0, _platformFeatureFlags.fg)('platform_editor_block_menu_patch_1') ? formatMessage(_messages.blockMenuMessages.deleteBlock) : formatMessage(_blockMenu.messages.deleteBlock);
86
98
  return /*#__PURE__*/_react.default.createElement(_box.Box, {
87
99
  onMouseEnter: onShowHoverDecoration,
@@ -1,5 +1,6 @@
1
1
  import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
2
2
  import { findChildrenByType } from '@atlaskit/editor-prosemirror/utils';
3
+ import { fg } from '@atlaskit/platform-feature-flags';
3
4
  import { getInlineNodeTextContent } from './inline-node-transforms';
4
5
  import { isBlockNodeType, isListNodeType, isContainerNodeType, isBlockNodeForExtraction, convertNodeToInlineContent, getContentSupportChecker, convertCodeBlockContentToParagraphs, filterMarksForTargetNodeType, getMarksWithBreakout } from './utils';
5
6
  const convertInvalidNodeToValidNodeType = (sourceContent, sourceNodeType, validNodeType, withMarks) => {
@@ -204,7 +205,11 @@ export const unwrapAndConvertToList = ({
204
205
  } = schema.nodes;
205
206
  const isTargetTaskList = targetNodeType === taskList;
206
207
  const createListItemFromInline = content => {
207
- return isTargetTaskList ? taskItem.create(null, content) : listItem.create(null, paragraph.create(null, content));
208
+ if (!content && fg('platform_editor_block_menu_patch_2')) {
209
+ return isTargetTaskList ? taskItem.create() : listItem.create(null, paragraph.create());
210
+ } else {
211
+ return isTargetTaskList ? taskItem.create(null, content) : listItem.create(null, paragraph.create(null, content));
212
+ }
208
213
  };
209
214
  const getInlineContent = textblock => {
210
215
  const inlineContent = [];
@@ -234,6 +239,13 @@ export const unwrapAndConvertToList = ({
234
239
  };
235
240
  if (sourceNode.type.name === 'codeBlock') {
236
241
  const codeText = sourceNode.textContent;
242
+ // check if code block only contains newline characters
243
+ // eslint-disable-next-line require-unicode-regexp
244
+ const isOnlyNewLines = codeText => codeText.replace(/\n/g, '').trim() === '';
245
+ if ((!codeText || isOnlyNewLines(codeText)) && fg('platform_editor_block_menu_patch_2')) {
246
+ // Empty code block - create an empty list item
247
+ currentListItems.push(createListItemFromInline());
248
+ }
237
249
  if (codeText) {
238
250
  const lines = codeText.split('\n');
239
251
  // Remove empty lines
@@ -1,5 +1,6 @@
1
1
  import React, { Fragment } from 'react';
2
2
  import { ArrowKeyNavigationProvider, ArrowKeyNavigationType } from '@atlaskit/editor-common/ui-menu';
3
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
3
4
  const NoOp = props => null;
4
5
  const isNonNestedMenuSection = component => {
5
6
  return component.type === 'block-menu-section' && !('parent' in component);
@@ -29,48 +30,51 @@ export const BlockMenuRenderer = ({
29
30
  const menuSections = getSortedNonNestedSections(components);
30
31
  const menuItems = components.filter(isMenuItem);
31
32
  const nestedMenus = components.filter(isNestedMenu);
32
- return /*#__PURE__*/React.createElement(ArrowKeyNavigationProvider, {
33
- type: ArrowKeyNavigationType.MENU
34
- }, /*#__PURE__*/React.createElement(Fragment, null, menuSections.map(section => {
35
- // Get all items for the current section, including nested menus, and sort them by rank
36
- const currentSectionItemsSorted = getSortedChildren([...menuItems, ...nestedMenus], section.key);
37
- if (currentSectionItemsSorted.length === 0) {
38
- return null;
39
- }
33
+ const renderMenu = () => {
34
+ return /*#__PURE__*/React.createElement(Fragment, null, menuSections.map(section => {
35
+ // Get all items for the current section, including nested menus, and sort them by rank
36
+ const currentSectionItemsSorted = getSortedChildren([...menuItems, ...nestedMenus], section.key);
37
+ if (currentSectionItemsSorted.length === 0) {
38
+ return null;
39
+ }
40
40
 
41
- // iterate over the current section items, if it is nested menu, get their children, sort them
42
- // if they are menu items, just render as they are sorted above
43
- const getChildrenWithNestedItems = items => {
44
- return items.map(item => {
45
- if (isNestedMenu(item)) {
46
- const sortedNestedSections = getSortedNestedSections(components, item.key);
47
- return sortedNestedSections.map(section => {
48
- const sortedNestedMenuItems = getSortedChildren(menuItems, section.key);
49
- const NestedMenuComponent = item.component || fallbacks.nestedMenu || NoOp;
50
- const NestedSection = section.component || fallbacks.section || NoOp;
51
- return /*#__PURE__*/React.createElement(NestedMenuComponent, {
41
+ // iterate over the current section items, if it is nested menu, get their children, sort them
42
+ // if they are menu items, just render as they are sorted above
43
+ const getChildrenWithNestedItems = items => {
44
+ return items.map(item => {
45
+ if (isNestedMenu(item)) {
46
+ const sortedNestedSections = getSortedNestedSections(components, item.key);
47
+ return sortedNestedSections.map(section => {
48
+ const sortedNestedMenuItems = getSortedChildren(menuItems, section.key);
49
+ const NestedMenuComponent = item.component || fallbacks.nestedMenu || NoOp;
50
+ const NestedSection = section.component || fallbacks.section || NoOp;
51
+ return /*#__PURE__*/React.createElement(NestedMenuComponent, {
52
+ key: item.key
53
+ }, /*#__PURE__*/React.createElement(NestedSection, {
54
+ key: section.key
55
+ }, sortedNestedMenuItems.map(nestedItem => {
56
+ const NestedMenuItemComponent = nestedItem.component || fallbacks.item || NoOp;
57
+ return /*#__PURE__*/React.createElement(NestedMenuItemComponent, {
58
+ key: nestedItem.key
59
+ });
60
+ })));
61
+ });
62
+ } else {
63
+ const ItemComponent = item.component || fallbacks.item || NoOp;
64
+ return /*#__PURE__*/React.createElement(ItemComponent, {
52
65
  key: item.key
53
- }, /*#__PURE__*/React.createElement(NestedSection, {
54
- key: section.key
55
- }, sortedNestedMenuItems.map(nestedItem => {
56
- const NestedMenuItemComponent = nestedItem.component || fallbacks.item || NoOp;
57
- return /*#__PURE__*/React.createElement(NestedMenuItemComponent, {
58
- key: nestedItem.key
59
- });
60
- })));
61
- });
62
- } else {
63
- const ItemComponent = item.component || fallbacks.item || NoOp;
64
- return /*#__PURE__*/React.createElement(ItemComponent, {
65
- key: item.key
66
- });
67
- }
68
- });
69
- };
70
- const children = getChildrenWithNestedItems(currentSectionItemsSorted);
71
- const SectionComponent = section.component || fallbacks.section || NoOp;
72
- return /*#__PURE__*/React.createElement(SectionComponent, {
73
- key: section.key
74
- }, children);
75
- })));
66
+ });
67
+ }
68
+ });
69
+ };
70
+ const children = getChildrenWithNestedItems(currentSectionItemsSorted);
71
+ const SectionComponent = section.component || fallbacks.section || NoOp;
72
+ return /*#__PURE__*/React.createElement(SectionComponent, {
73
+ key: section.key
74
+ }, children);
75
+ }));
76
+ };
77
+ return expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true) ? /*#__PURE__*/React.createElement(ArrowKeyNavigationProvider, {
78
+ type: ArrowKeyNavigationType.MENU
79
+ }, renderMenu()) : renderMenu();
76
80
  };
@@ -55,14 +55,16 @@ const BlockMenu = ({
55
55
  menuTriggerBy,
56
56
  isSelectedViaDragHandle,
57
57
  isMenuOpen,
58
- currentUserIntent
58
+ currentUserIntent,
59
+ openedViaKeyboard
59
60
  } = useSharedPluginStateWithSelector(api, ['blockControls', 'userIntent'], states => {
60
- var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$userIntentSta;
61
+ var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$userIntentSta, _states$blockControls4, _states$blockControls5;
61
62
  return {
62
63
  menuTriggerBy: (_states$blockControls = states.blockControlsState) === null || _states$blockControls === void 0 ? void 0 : _states$blockControls.menuTriggerBy,
63
64
  isSelectedViaDragHandle: (_states$blockControls2 = states.blockControlsState) === null || _states$blockControls2 === void 0 ? void 0 : _states$blockControls2.isSelectedViaDragHandle,
64
65
  isMenuOpen: (_states$blockControls3 = states.blockControlsState) === null || _states$blockControls3 === void 0 ? void 0 : _states$blockControls3.isMenuOpen,
65
- currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent
66
+ currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent,
67
+ 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
66
68
  };
67
69
  });
68
70
  const {
@@ -76,7 +78,7 @@ const BlockMenu = ({
76
78
  // hasSelection true, always show block menu
77
79
  // hasSelection false, only show block menu when empty line experiment is enabled
78
80
  const shouldShowBlockMenuForEmptyLine = hasSelection || !hasSelection && expValEqualsNoExposure('platform_editor_block_menu_empty_line', 'isEnabled', true);
79
- const selectedByShortcutORDragHandle = isSelectedViaDragHandle;
81
+ const selectedByShortcutORDragHandle = isSelectedViaDragHandle || openedViaKeyboard && expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true);
80
82
  useEffect(() => {
81
83
  var _api$userIntent;
82
84
  if (!isMenuOpen || !menuTriggerBy || !selectedByShortcutORDragHandle || !hasFocus || !shouldShowBlockMenuForEmptyLine || ['resizing', 'dragging'].includes(currentUserIntent || '')) {
@@ -140,7 +142,9 @@ const BlockMenu = ({
140
142
  preventOverflow: true // disables forced horizontal placement when forcePlacement is on, so fitWidth controls flipping
141
143
  ,
142
144
  stick: true,
143
- focusTrap: true,
145
+ focusTrap: expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true) ? openedViaKeyboard ? {
146
+ initialFocus: undefined
147
+ } : true : undefined,
144
148
  offset: [DRAG_HANDLE_WIDTH + DRAG_HANDLE_OFFSET_PADDING, 0]
145
149
  }, /*#__PURE__*/React.createElement(BlockMenuContent, {
146
150
  api: api
@@ -1,4 +1,4 @@
1
- import React, { useCallback } from 'react';
1
+ import React, { useCallback, useEffect } from 'react';
2
2
  import { useIntl, injectIntl } from 'react-intl-next';
3
3
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
4
4
  import { messages } from '@atlaskit/editor-common/block-menu';
@@ -74,10 +74,22 @@ const DeleteDropdownItemContent = ({
74
74
  return tr;
75
75
  });
76
76
  }, [api, nodeTypes]);
77
- const onRemoveHoverDecoration = () => {
77
+ const onRemoveHoverDecoration = useCallback(() => {
78
78
  var _api$decorations2, _api$decorations2$com, _api$decorations2$com2;
79
79
  api === null || api === void 0 ? void 0 : api.core.actions.execute(api === null || api === void 0 ? void 0 : (_api$decorations2 = api.decorations) === null || _api$decorations2 === void 0 ? void 0 : (_api$decorations2$com = _api$decorations2.commands) === null || _api$decorations2$com === void 0 ? void 0 : (_api$decorations2$com2 = _api$decorations2$com.removeDecoration) === null || _api$decorations2$com2 === void 0 ? void 0 : _api$decorations2$com2.call(_api$decorations2$com));
80
- };
80
+ }, [api]);
81
+ useEffect(() => {
82
+ if (!expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true)) {
83
+ return;
84
+ }
85
+ return () => {
86
+ if (!expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true)) {
87
+ return;
88
+ }
89
+ // clean up hover decoration when unmounting
90
+ onRemoveHoverDecoration();
91
+ };
92
+ }, [onRemoveHoverDecoration]);
81
93
  const text = fg('platform_editor_block_menu_patch_1') ? formatMessage(blockMenuMessages.deleteBlock) : formatMessage(messages.deleteBlock);
82
94
  return /*#__PURE__*/React.createElement(Box, {
83
95
  onMouseEnter: onShowHoverDecoration,
@@ -1,6 +1,7 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
2
  import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
3
3
  import { findChildrenByType } from '@atlaskit/editor-prosemirror/utils';
4
+ import { fg } from '@atlaskit/platform-feature-flags';
4
5
  import { getInlineNodeTextContent } from './inline-node-transforms';
5
6
  import { isBlockNodeType, isListNodeType, isContainerNodeType, isBlockNodeForExtraction, convertNodeToInlineContent, getContentSupportChecker, convertCodeBlockContentToParagraphs, filterMarksForTargetNodeType, getMarksWithBreakout } from './utils';
6
7
  var convertInvalidNodeToValidNodeType = function convertInvalidNodeToValidNodeType(sourceContent, sourceNodeType, validNodeType, withMarks) {
@@ -196,7 +197,11 @@ export var unwrapAndConvertToList = function unwrapAndConvertToList(_ref3) {
196
197
  heading = _schema$nodes2.heading;
197
198
  var isTargetTaskList = targetNodeType === taskList;
198
199
  var createListItemFromInline = function createListItemFromInline(content) {
199
- return isTargetTaskList ? taskItem.create(null, content) : listItem.create(null, paragraph.create(null, content));
200
+ if (!content && fg('platform_editor_block_menu_patch_2')) {
201
+ return isTargetTaskList ? taskItem.create() : listItem.create(null, paragraph.create());
202
+ } else {
203
+ return isTargetTaskList ? taskItem.create(null, content) : listItem.create(null, paragraph.create(null, content));
204
+ }
200
205
  };
201
206
  var getInlineContent = function getInlineContent(textblock) {
202
207
  var inlineContent = [];
@@ -226,6 +231,15 @@ export var unwrapAndConvertToList = function unwrapAndConvertToList(_ref3) {
226
231
  };
227
232
  if (sourceNode.type.name === 'codeBlock') {
228
233
  var codeText = sourceNode.textContent;
234
+ // check if code block only contains newline characters
235
+ // eslint-disable-next-line require-unicode-regexp
236
+ var isOnlyNewLines = function isOnlyNewLines(codeText) {
237
+ return codeText.replace(/\n/g, '').trim() === '';
238
+ };
239
+ if ((!codeText || isOnlyNewLines(codeText)) && fg('platform_editor_block_menu_patch_2')) {
240
+ // Empty code block - create an empty list item
241
+ currentListItems.push(createListItemFromInline());
242
+ }
229
243
  if (codeText) {
230
244
  var lines = codeText.split('\n');
231
245
  // Remove empty lines
@@ -1,6 +1,7 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
2
  import React, { Fragment } from 'react';
3
3
  import { ArrowKeyNavigationProvider, ArrowKeyNavigationType } from '@atlaskit/editor-common/ui-menu';
4
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
4
5
  var NoOp = function NoOp(props) {
5
6
  return null;
6
7
  };
@@ -41,48 +42,51 @@ export var BlockMenuRenderer = function BlockMenuRenderer(_ref) {
41
42
  var menuSections = getSortedNonNestedSections(components);
42
43
  var menuItems = components.filter(isMenuItem);
43
44
  var nestedMenus = components.filter(isNestedMenu);
44
- return /*#__PURE__*/React.createElement(ArrowKeyNavigationProvider, {
45
- type: ArrowKeyNavigationType.MENU
46
- }, /*#__PURE__*/React.createElement(Fragment, null, menuSections.map(function (section) {
47
- // Get all items for the current section, including nested menus, and sort them by rank
48
- var currentSectionItemsSorted = getSortedChildren([].concat(_toConsumableArray(menuItems), _toConsumableArray(nestedMenus)), section.key);
49
- if (currentSectionItemsSorted.length === 0) {
50
- return null;
51
- }
45
+ var renderMenu = function renderMenu() {
46
+ return /*#__PURE__*/React.createElement(Fragment, null, menuSections.map(function (section) {
47
+ // Get all items for the current section, including nested menus, and sort them by rank
48
+ var currentSectionItemsSorted = getSortedChildren([].concat(_toConsumableArray(menuItems), _toConsumableArray(nestedMenus)), section.key);
49
+ if (currentSectionItemsSorted.length === 0) {
50
+ return null;
51
+ }
52
52
 
53
- // iterate over the current section items, if it is nested menu, get their children, sort them
54
- // if they are menu items, just render as they are sorted above
55
- var getChildrenWithNestedItems = function getChildrenWithNestedItems(items) {
56
- return items.map(function (item) {
57
- if (isNestedMenu(item)) {
58
- var sortedNestedSections = getSortedNestedSections(components, item.key);
59
- return sortedNestedSections.map(function (section) {
60
- var sortedNestedMenuItems = getSortedChildren(menuItems, section.key);
61
- var NestedMenuComponent = item.component || fallbacks.nestedMenu || NoOp;
62
- var NestedSection = section.component || fallbacks.section || NoOp;
63
- return /*#__PURE__*/React.createElement(NestedMenuComponent, {
53
+ // iterate over the current section items, if it is nested menu, get their children, sort them
54
+ // if they are menu items, just render as they are sorted above
55
+ var getChildrenWithNestedItems = function getChildrenWithNestedItems(items) {
56
+ return items.map(function (item) {
57
+ if (isNestedMenu(item)) {
58
+ var sortedNestedSections = getSortedNestedSections(components, item.key);
59
+ return sortedNestedSections.map(function (section) {
60
+ var sortedNestedMenuItems = getSortedChildren(menuItems, section.key);
61
+ var NestedMenuComponent = item.component || fallbacks.nestedMenu || NoOp;
62
+ var NestedSection = section.component || fallbacks.section || NoOp;
63
+ return /*#__PURE__*/React.createElement(NestedMenuComponent, {
64
+ key: item.key
65
+ }, /*#__PURE__*/React.createElement(NestedSection, {
66
+ key: section.key
67
+ }, sortedNestedMenuItems.map(function (nestedItem) {
68
+ var NestedMenuItemComponent = nestedItem.component || fallbacks.item || NoOp;
69
+ return /*#__PURE__*/React.createElement(NestedMenuItemComponent, {
70
+ key: nestedItem.key
71
+ });
72
+ })));
73
+ });
74
+ } else {
75
+ var ItemComponent = item.component || fallbacks.item || NoOp;
76
+ return /*#__PURE__*/React.createElement(ItemComponent, {
64
77
  key: item.key
65
- }, /*#__PURE__*/React.createElement(NestedSection, {
66
- key: section.key
67
- }, sortedNestedMenuItems.map(function (nestedItem) {
68
- var NestedMenuItemComponent = nestedItem.component || fallbacks.item || NoOp;
69
- return /*#__PURE__*/React.createElement(NestedMenuItemComponent, {
70
- key: nestedItem.key
71
- });
72
- })));
73
- });
74
- } else {
75
- var ItemComponent = item.component || fallbacks.item || NoOp;
76
- return /*#__PURE__*/React.createElement(ItemComponent, {
77
- key: item.key
78
- });
79
- }
80
- });
81
- };
82
- var children = getChildrenWithNestedItems(currentSectionItemsSorted);
83
- var SectionComponent = section.component || fallbacks.section || NoOp;
84
- return /*#__PURE__*/React.createElement(SectionComponent, {
85
- key: section.key
86
- }, children);
87
- })));
78
+ });
79
+ }
80
+ });
81
+ };
82
+ var children = getChildrenWithNestedItems(currentSectionItemsSorted);
83
+ var SectionComponent = section.component || fallbacks.section || NoOp;
84
+ return /*#__PURE__*/React.createElement(SectionComponent, {
85
+ key: section.key
86
+ }, children);
87
+ }));
88
+ };
89
+ return expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true) ? /*#__PURE__*/React.createElement(ArrowKeyNavigationProvider, {
90
+ type: ArrowKeyNavigationType.MENU
91
+ }, renderMenu()) : renderMenu();
88
92
  };
@@ -56,18 +56,20 @@ var BlockMenu = function BlockMenu(_ref2) {
56
56
  boundariesElement = _ref2.boundariesElement,
57
57
  scrollableElement = _ref2.scrollableElement;
58
58
  var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['blockControls', 'userIntent'], function (states) {
59
- var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$userIntentSta;
59
+ var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$userIntentSta, _states$blockControls4;
60
60
  return {
61
61
  menuTriggerBy: (_states$blockControls = states.blockControlsState) === null || _states$blockControls === void 0 ? void 0 : _states$blockControls.menuTriggerBy,
62
62
  isSelectedViaDragHandle: (_states$blockControls2 = states.blockControlsState) === null || _states$blockControls2 === void 0 ? void 0 : _states$blockControls2.isSelectedViaDragHandle,
63
63
  isMenuOpen: (_states$blockControls3 = states.blockControlsState) === null || _states$blockControls3 === void 0 ? void 0 : _states$blockControls3.isMenuOpen,
64
- currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent
64
+ currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent,
65
+ 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
65
66
  };
66
67
  }),
67
68
  menuTriggerBy = _useSharedPluginState.menuTriggerBy,
68
69
  isSelectedViaDragHandle = _useSharedPluginState.isSelectedViaDragHandle,
69
70
  isMenuOpen = _useSharedPluginState.isMenuOpen,
70
- currentUserIntent = _useSharedPluginState.currentUserIntent;
71
+ currentUserIntent = _useSharedPluginState.currentUserIntent,
72
+ openedViaKeyboard = _useSharedPluginState.openedViaKeyboard;
71
73
  var _useBlockMenu = useBlockMenu(),
72
74
  onDropdownOpenChanged = _useBlockMenu.onDropdownOpenChanged,
73
75
  fireAnalyticsEvent = _useBlockMenu.fireAnalyticsEvent;
@@ -78,7 +80,7 @@ var BlockMenu = function BlockMenu(_ref2) {
78
80
  // hasSelection true, always show block menu
79
81
  // hasSelection false, only show block menu when empty line experiment is enabled
80
82
  var shouldShowBlockMenuForEmptyLine = hasSelection || !hasSelection && expValEqualsNoExposure('platform_editor_block_menu_empty_line', 'isEnabled', true);
81
- var selectedByShortcutORDragHandle = isSelectedViaDragHandle;
83
+ var selectedByShortcutORDragHandle = isSelectedViaDragHandle || openedViaKeyboard && expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true);
82
84
  useEffect(function () {
83
85
  var _api$userIntent;
84
86
  if (!isMenuOpen || !menuTriggerBy || !selectedByShortcutORDragHandle || !hasFocus || !shouldShowBlockMenuForEmptyLine || ['resizing', 'dragging'].includes(currentUserIntent || '')) {
@@ -141,7 +143,9 @@ var BlockMenu = function BlockMenu(_ref2) {
141
143
  preventOverflow: true // disables forced horizontal placement when forcePlacement is on, so fitWidth controls flipping
142
144
  ,
143
145
  stick: true,
144
- focusTrap: true,
146
+ focusTrap: expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true) ? openedViaKeyboard ? {
147
+ initialFocus: undefined
148
+ } : true : undefined,
145
149
  offset: [DRAG_HANDLE_WIDTH + DRAG_HANDLE_OFFSET_PADDING, 0]
146
150
  }, /*#__PURE__*/React.createElement(BlockMenuContent, {
147
151
  api: api
@@ -1,4 +1,4 @@
1
- import React, { useCallback } from 'react';
1
+ import React, { useCallback, useEffect } from 'react';
2
2
  import { useIntl, injectIntl } from 'react-intl-next';
3
3
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
4
4
  import { messages } from '@atlaskit/editor-common/block-menu';
@@ -69,10 +69,22 @@ var DeleteDropdownItemContent = function DeleteDropdownItemContent(_ref) {
69
69
  return tr;
70
70
  });
71
71
  }, [api, nodeTypes]);
72
- var onRemoveHoverDecoration = function onRemoveHoverDecoration() {
72
+ var onRemoveHoverDecoration = useCallback(function () {
73
73
  var _api$decorations2, _api$decorations2$rem;
74
74
  api === null || api === void 0 || api.core.actions.execute(api === null || api === void 0 || (_api$decorations2 = api.decorations) === null || _api$decorations2 === void 0 || (_api$decorations2 = _api$decorations2.commands) === null || _api$decorations2 === void 0 || (_api$decorations2$rem = _api$decorations2.removeDecoration) === null || _api$decorations2$rem === void 0 ? void 0 : _api$decorations2$rem.call(_api$decorations2));
75
- };
75
+ }, [api]);
76
+ useEffect(function () {
77
+ if (!expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true)) {
78
+ return;
79
+ }
80
+ return function () {
81
+ if (!expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true)) {
82
+ return;
83
+ }
84
+ // clean up hover decoration when unmounting
85
+ onRemoveHoverDecoration();
86
+ };
87
+ }, [onRemoveHoverDecoration]);
76
88
  var text = fg('platform_editor_block_menu_patch_1') ? formatMessage(blockMenuMessages.deleteBlock) : formatMessage(messages.deleteBlock);
77
89
  return /*#__PURE__*/React.createElement(Box, {
78
90
  onMouseEnter: onShowHoverDecoration,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-block-menu",
3
- "version": "4.0.3",
3
+ "version": "4.0.4",
4
4
  "description": "BlockMenu plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -31,7 +31,7 @@
31
31
  "@atlaskit/css": "^0.14.0",
32
32
  "@atlaskit/dropdown-menu": "^16.3.0",
33
33
  "@atlaskit/editor-plugin-analytics": "^6.1.0",
34
- "@atlaskit/editor-plugin-block-controls": "^7.1.0",
34
+ "@atlaskit/editor-plugin-block-controls": "^7.2.0",
35
35
  "@atlaskit/editor-plugin-decorations": "^6.1.0",
36
36
  "@atlaskit/editor-plugin-selection": "^6.0.0",
37
37
  "@atlaskit/editor-plugin-user-intent": "^4.0.0",
@@ -48,7 +48,7 @@
48
48
  "@babel/runtime": "^7.0.0"
49
49
  },
50
50
  "peerDependencies": {
51
- "@atlaskit/editor-common": "^110.3.0",
51
+ "@atlaskit/editor-common": "^110.4.0",
52
52
  "react": "^18.2.0",
53
53
  "react-intl-next": "npm:react-intl@^5.18.1"
54
54
  },