@atlaskit/editor-plugin-block-menu 5.1.3 → 5.1.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.
Files changed (25) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/cjs/editor-commands/transform-node-utils/flattenListStep.js +108 -0
  3. package/dist/cjs/editor-commands/transform-node-utils/transform.js +3 -1
  4. package/dist/cjs/editor-commands/transform-node-utils/unwrapListStep.js +28 -0
  5. package/dist/cjs/editor-commands/transformNode.js +8 -5
  6. package/dist/cjs/ui/block-menu.js +20 -7
  7. package/dist/es2019/editor-commands/transform-node-utils/flattenListStep.js +94 -0
  8. package/dist/es2019/editor-commands/transform-node-utils/transform.js +3 -1
  9. package/dist/es2019/editor-commands/transform-node-utils/unwrapListStep.js +18 -0
  10. package/dist/es2019/editor-commands/transformNode.js +8 -6
  11. package/dist/es2019/ui/block-menu.js +15 -7
  12. package/dist/esm/editor-commands/transform-node-utils/flattenListStep.js +102 -0
  13. package/dist/esm/editor-commands/transform-node-utils/transform.js +3 -1
  14. package/dist/esm/editor-commands/transform-node-utils/unwrapListStep.js +21 -0
  15. package/dist/esm/editor-commands/transformNode.js +8 -5
  16. package/dist/esm/ui/block-menu.js +19 -7
  17. package/dist/types/editor-commands/transform-node-utils/flattenListStep.d.ts +49 -0
  18. package/dist/types/editor-commands/transform-node-utils/transform.d.ts +1 -1
  19. package/dist/types/editor-commands/transform-node-utils/unwrapListStep.d.ts +7 -0
  20. package/dist/types/editor-commands/transforms/types.d.ts +1 -1
  21. package/dist/types-ts4.5/editor-commands/transform-node-utils/flattenListStep.d.ts +49 -0
  22. package/dist/types-ts4.5/editor-commands/transform-node-utils/transform.d.ts +1 -1
  23. package/dist/types-ts4.5/editor-commands/transform-node-utils/unwrapListStep.d.ts +7 -0
  24. package/dist/types-ts4.5/editor-commands/transforms/types.d.ts +1 -1
  25. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @atlaskit/editor-plugin-block-menu
2
2
 
3
+ ## 5.1.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [`7e5df3d5beaf3`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/7e5df3d5beaf3) -
8
+ Add new flattenListStep and unwrapListStep and use for list -> paragraph step. Also moved
9
+ expandToBlockRange util function to editor-common to re-use
10
+ - Updated dependencies
11
+
3
12
  ## 5.1.3
4
13
 
5
14
  ### Patch Changes
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.flattenListStep = void 0;
7
+ var _model = require("@atlaskit/editor-prosemirror/model");
8
+ var extractNestedLists = function extractNestedLists(node, listTypes, itemTypes) {
9
+ var items = [];
10
+ var _extract = function extract(currentNode) {
11
+ currentNode.forEach(function (child) {
12
+ // list item -> take content without nested lists, then recurse into nested lists
13
+ if (itemTypes.some(function (type) {
14
+ return child.type === type;
15
+ })) {
16
+ // Filter out nested list nodes from the list item's content
17
+ var contentWithoutNestedLists = [];
18
+ var nestedLists = [];
19
+ child.forEach(function (grandChild) {
20
+ if (listTypes.some(function (type) {
21
+ return grandChild.type === type;
22
+ })) {
23
+ // This is a nested list - collect it for later processing
24
+ nestedLists.push(grandChild);
25
+ } else {
26
+ // This is regular content (paragraph, etc.) - keep it
27
+ contentWithoutNestedLists.push(grandChild);
28
+ }
29
+ });
30
+
31
+ // Add the list item with only its non-list content
32
+ items.push(child.copy(_model.Fragment.from(contentWithoutNestedLists)));
33
+
34
+ // Now process nested lists to maintain document order
35
+ nestedLists.forEach(function (nestedList) {
36
+ _extract(nestedList);
37
+ });
38
+ }
39
+ // lists -> keep operating
40
+ else if (listTypes.some(function (type) {
41
+ return child.type === type;
42
+ })) {
43
+ _extract(child);
44
+ }
45
+ });
46
+ };
47
+ _extract(node);
48
+ return items;
49
+ };
50
+
51
+ /**
52
+ * Given an array of nodes, returns an array with the flattened children of any list node
53
+ * to it's first ancestor list, maintaining document order.
54
+ *
55
+ * @example
56
+ * Input:
57
+ * - bulletList
58
+ * - listItem "A"
59
+ * - listItem "B"
60
+ * - bulletList
61
+ * - listItem "C"
62
+ * - listItem "D"
63
+ * - listItem "E"
64
+ *
65
+ * Output:
66
+ * - bulletList
67
+ * - listItem "A"
68
+ * - listItem "B"
69
+ * - listItem "C"
70
+ * - listItem "D"
71
+ * - listItem "E"
72
+ *
73
+ * @example
74
+ * Input (deeply nested):
75
+ * - bulletList
76
+ * - listItem "1"
77
+ * - bulletList
78
+ * - listItem "1.1"
79
+ * - bulletList
80
+ * - listItem "1.1.1"
81
+ * - listItem "1.2"
82
+ * - listItem "2"
83
+ *
84
+ * Output:
85
+ * - bulletList
86
+ * - listItem "1"
87
+ * - listItem "1.1"
88
+ * - listItem "1.1.1"
89
+ * - listItem "1.2"
90
+ * - listItem "2"
91
+ *
92
+ * @param nodes
93
+ * @param context
94
+ * @returns
95
+ *
96
+ * TODO: Lists with mixed types (e.g. bulletList with a taskItem) doesn't full flatten
97
+ */
98
+ var flattenListStep = exports.flattenListStep = function flattenListStep(nodes, context) {
99
+ var listTypes = [context.schema.nodes.bulletList, context.schema.nodes.orderedList, context.schema.nodes.taskList];
100
+ return nodes.map(function (node) {
101
+ if (listTypes.some(function (type) {
102
+ return node.type === type;
103
+ })) {
104
+ return node.copy(_model.Fragment.from(extractNestedLists(node, listTypes, [context.schema.nodes.listItem, context.schema.nodes.taskItem])));
105
+ }
106
+ return node;
107
+ });
108
+ };
@@ -4,8 +4,10 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.getOutputNodes = void 0;
7
+ var _flattenListStep = require("./flattenListStep");
7
8
  var _stubStep = require("./stubStep");
8
9
  var _types = require("./types");
10
+ var _unwrapListStep = require("./unwrapListStep");
9
11
  // Exampled step for overrides:
10
12
  // - open Block menu on a paragraph, click 'Panel' in the Turn into'
11
13
  // - expected to put paragraph into a panel
@@ -32,7 +34,7 @@ var TRANSFORM_STEPS = {
32
34
  atomic: undefined,
33
35
  container: [_stubStep.stubStep],
34
36
  list: [_stubStep.stubStep],
35
- text: [_stubStep.stubStep]
37
+ text: [_flattenListStep.flattenListStep, _unwrapListStep.unwrapListStep]
36
38
  },
37
39
  text: {
38
40
  atomic: undefined,
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.unwrapListStep = void 0;
8
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
+ /**
10
+ * Given an array of nodes, returns an array with the flattened children of any list nodes.
11
+ * @param nodes
12
+ * @returns
13
+ */
14
+ var unwrapListStep = exports.unwrapListStep = function unwrapListStep(nodes, context) {
15
+ var listTypes = [context.schema.nodes.bulletList, context.schema.nodes.orderedList, context.schema.nodes.taskList];
16
+ return nodes.flatMap(function (node) {
17
+ if (listTypes.some(function (type) {
18
+ return node.type === type;
19
+ })) {
20
+ var listItems = [];
21
+ node.forEach(function (listItem) {
22
+ listItems.push.apply(listItems, (0, _toConsumableArray2.default)(listItem.children));
23
+ });
24
+ return listItems;
25
+ }
26
+ return node;
27
+ });
28
+ };
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.transformNode = void 0;
7
+ var _selection = require("@atlaskit/editor-common/selection");
7
8
  var _model = require("@atlaskit/editor-prosemirror/model");
8
9
  var _transform = require("./transform-node-utils/transform");
9
10
  var _utils = require("./transforms/utils");
@@ -18,13 +19,13 @@ var transformNode = exports.transformNode = function transformNode(api) {
18
19
  if (!preservedSelection) {
19
20
  return tr;
20
21
  }
21
- var from = preservedSelection.from,
22
- to = preservedSelection.to,
23
- $from = preservedSelection.$from;
22
+ var _expandToBlockRange = (0, _selection.expandToBlockRange)(preservedSelection.$from, preservedSelection.$to),
23
+ $from = _expandToBlockRange.$from,
24
+ $to = _expandToBlockRange.$to;
24
25
  var selectedParent = $from.parent;
25
26
  var fragment = _model.Fragment.empty;
26
27
  var isList = (0, _utils.isListNode)(selectedParent);
27
- var slice = tr.doc.slice(isList ? from - 1 : from, isList ? to + 1 : to);
28
+ var slice = tr.doc.slice(isList ? $from.pos - 1 : $from.pos, isList ? $to.pos + 1 : $to.pos);
28
29
  slice.content.forEach(function (node) {
29
30
  var outputNode = (0, _transform.getOutputNodes)({
30
31
  sourceNode: node,
@@ -35,7 +36,9 @@ var transformNode = exports.transformNode = function transformNode(api) {
35
36
  fragment = fragment.append(_model.Fragment.fromArray(outputNode));
36
37
  }
37
38
  });
38
- tr.replaceWith(isList ? preservedSelection.from - 1 : preservedSelection.from, preservedSelection.to, fragment);
39
+
40
+ // TODO: ED-12345 - selection is broken post transaction, to fix.
41
+ tr.replaceWith(isList ? $from.pos - 1 : $from.pos, $to.pos, fragment);
39
42
  return tr;
40
43
  };
41
44
  }
@@ -1,6 +1,7 @@
1
1
  /* block-menu.tsx generated by @compiled/babel-plugin v0.38.1 */
2
2
  "use strict";
3
3
 
4
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5
  var _typeof = require("@babel/runtime/helpers/typeof");
5
6
  Object.defineProperty(exports, "__esModule", {
6
7
  value: true
@@ -8,6 +9,7 @@ Object.defineProperty(exports, "__esModule", {
8
9
  exports.default = void 0;
9
10
  require("./block-menu.compiled.css");
10
11
  var _runtime = require("@compiled/react/runtime");
12
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
11
13
  var _react = _interopRequireWildcard(require("react"));
12
14
  var _reactIntlNext = require("react-intl-next");
13
15
  var _css = require("@atlaskit/css");
@@ -33,6 +35,7 @@ var styles = {
33
35
  };
34
36
  var DEFAULT_MENU_WIDTH = 230;
35
37
  var DRAG_HANDLE_OFFSET_PADDING = 5;
38
+ var FALLBACK_MENU_HEIGHT = 300;
36
39
  var PopupWithListeners = (0, _uiReact.withReactEditorViewOuterListeners)(_ui.Popup);
37
40
  var useConditionalBlockMenuEffect = (0, _platformFeatureFlagsReact.conditionalHooksFactory)(function () {
38
41
  return (0, _platformFeatureFlags.fg)('platform_editor_toolbar_aifc_user_intent_fix');
@@ -163,6 +166,18 @@ var BlockMenu = function BlockMenu(_ref4) {
163
166
  var targetHandleRef = editorView === null || editorView === void 0 || (_editorView$dom = editorView.dom) === null || _editorView$dom === void 0 ? void 0 : _editorView$dom.querySelector(_styles.DRAG_HANDLE_SELECTOR);
164
167
  var prevIsMenuOpenRef = (0, _react.useRef)(false);
165
168
  var popupRef = (0, _react.useRef)(undefined);
169
+ var _React$useState = _react.default.useState(0),
170
+ _React$useState2 = (0, _slicedToArray2.default)(_React$useState, 2),
171
+ menuHeight = _React$useState2[0],
172
+ setMenuHeight = _React$useState2[1];
173
+ var targetHandleHeightOffset = -((targetHandleRef === null || targetHandleRef === void 0 ? void 0 : targetHandleRef.clientHeight) || 0);
174
+ _react.default.useLayoutEffect(function () {
175
+ var _popupRef$current;
176
+ if (!isMenuOpen) {
177
+ return;
178
+ }
179
+ setMenuHeight(((_popupRef$current = popupRef.current) === null || _popupRef$current === void 0 ? void 0 : _popupRef$current.clientHeight) || FALLBACK_MENU_HEIGHT);
180
+ }, [isMenuOpen]);
166
181
  var hasFocus = (_ref5 = (editorView === null || editorView === void 0 ? void 0 : editorView.hasFocus()) || document.activeElement === targetHandleRef || popupRef.current && (popupRef.current.contains(document.activeElement) || popupRef.current === document.activeElement)) !== null && _ref5 !== void 0 ? _ref5 : false;
167
182
  var selectedByShortcutOrDragHandle = !!isSelectedViaDragHandle || !!openedViaKeyboard;
168
183
 
@@ -226,8 +241,7 @@ var BlockMenu = function BlockMenu(_ref4) {
226
241
  fallbackComponent: null
227
242
  }, /*#__PURE__*/_react.default.createElement(PopupWithListeners, {
228
243
  alignX: 'right',
229
- alignY: 'start' // respected when forcePlacement is true
230
- ,
244
+ alignY: 'start',
231
245
  handleClickOutside: closeMenu,
232
246
  handleEscapeKeydown: closeMenu,
233
247
  handleBackspaceDeleteKeydown: handleBackspaceDeleteKeydown,
@@ -237,16 +251,15 @@ var BlockMenu = function BlockMenu(_ref4) {
237
251
  target: targetHandleRef,
238
252
  zIndex: _editorSharedStyles.akEditorFloatingOverlapPanelZIndex,
239
253
  fitWidth: DEFAULT_MENU_WIDTH,
240
- forcePlacement: true,
241
- preventOverflow: true // disables forced horizontal placement when forcePlacement is on, so fitWidth controls flipping
242
- ,
254
+ fitHeight: menuHeight,
255
+ preventOverflow: true,
243
256
  stick: true,
257
+ offset: [_styles.DRAG_HANDLE_WIDTH + DRAG_HANDLE_OFFSET_PADDING, targetHandleHeightOffset],
244
258
  focusTrap: openedViaKeyboard ?
245
259
  // Only enable focus trap when opened via keyboard to make sure the focus is on the first focusable menu item
246
260
  {
247
261
  initialFocus: undefined
248
- } : undefined,
249
- offset: [_styles.DRAG_HANDLE_WIDTH + DRAG_HANDLE_OFFSET_PADDING, 0]
262
+ } : undefined
250
263
  }, /*#__PURE__*/_react.default.createElement(BlockMenuContent, {
251
264
  api: api,
252
265
  setRef: setRef
@@ -0,0 +1,94 @@
1
+ import { Fragment } from '@atlaskit/editor-prosemirror/model';
2
+ const extractNestedLists = (node, listTypes, itemTypes) => {
3
+ const items = [];
4
+ const extract = currentNode => {
5
+ currentNode.forEach(child => {
6
+ // list item -> take content without nested lists, then recurse into nested lists
7
+ if (itemTypes.some(type => child.type === type)) {
8
+ // Filter out nested list nodes from the list item's content
9
+ const contentWithoutNestedLists = [];
10
+ const nestedLists = [];
11
+ child.forEach(grandChild => {
12
+ if (listTypes.some(type => grandChild.type === type)) {
13
+ // This is a nested list - collect it for later processing
14
+ nestedLists.push(grandChild);
15
+ } else {
16
+ // This is regular content (paragraph, etc.) - keep it
17
+ contentWithoutNestedLists.push(grandChild);
18
+ }
19
+ });
20
+
21
+ // Add the list item with only its non-list content
22
+ items.push(child.copy(Fragment.from(contentWithoutNestedLists)));
23
+
24
+ // Now process nested lists to maintain document order
25
+ nestedLists.forEach(nestedList => {
26
+ extract(nestedList);
27
+ });
28
+ }
29
+ // lists -> keep operating
30
+ else if (listTypes.some(type => child.type === type)) {
31
+ extract(child);
32
+ }
33
+ });
34
+ };
35
+ extract(node);
36
+ return items;
37
+ };
38
+
39
+ /**
40
+ * Given an array of nodes, returns an array with the flattened children of any list node
41
+ * to it's first ancestor list, maintaining document order.
42
+ *
43
+ * @example
44
+ * Input:
45
+ * - bulletList
46
+ * - listItem "A"
47
+ * - listItem "B"
48
+ * - bulletList
49
+ * - listItem "C"
50
+ * - listItem "D"
51
+ * - listItem "E"
52
+ *
53
+ * Output:
54
+ * - bulletList
55
+ * - listItem "A"
56
+ * - listItem "B"
57
+ * - listItem "C"
58
+ * - listItem "D"
59
+ * - listItem "E"
60
+ *
61
+ * @example
62
+ * Input (deeply nested):
63
+ * - bulletList
64
+ * - listItem "1"
65
+ * - bulletList
66
+ * - listItem "1.1"
67
+ * - bulletList
68
+ * - listItem "1.1.1"
69
+ * - listItem "1.2"
70
+ * - listItem "2"
71
+ *
72
+ * Output:
73
+ * - bulletList
74
+ * - listItem "1"
75
+ * - listItem "1.1"
76
+ * - listItem "1.1.1"
77
+ * - listItem "1.2"
78
+ * - listItem "2"
79
+ *
80
+ * @param nodes
81
+ * @param context
82
+ * @returns
83
+ *
84
+ * TODO: Lists with mixed types (e.g. bulletList with a taskItem) doesn't full flatten
85
+ */
86
+ export const flattenListStep = (nodes, context) => {
87
+ const listTypes = [context.schema.nodes.bulletList, context.schema.nodes.orderedList, context.schema.nodes.taskList];
88
+ return nodes.map(node => {
89
+ if (listTypes.some(type => node.type === type)) {
90
+ return node.copy(Fragment.from(extractNestedLists(node, listTypes, [context.schema.nodes.listItem, context.schema.nodes.taskItem])));
91
+ }
92
+ return node;
93
+ });
94
+ };
@@ -1,5 +1,7 @@
1
+ import { flattenListStep } from './flattenListStep';
1
2
  import { stubStep } from './stubStep';
2
3
  import { NODE_CATEGORY_BY_TYPE, toNodeTypeValue } from './types';
4
+ import { unwrapListStep } from './unwrapListStep';
3
5
 
4
6
  // Exampled step for overrides:
5
7
  // - open Block menu on a paragraph, click 'Panel' in the Turn into'
@@ -27,7 +29,7 @@ const TRANSFORM_STEPS = {
27
29
  atomic: undefined,
28
30
  container: [stubStep],
29
31
  list: [stubStep],
30
- text: [stubStep]
32
+ text: [flattenListStep, unwrapListStep]
31
33
  },
32
34
  text: {
33
35
  atomic: undefined,
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Given an array of nodes, returns an array with the flattened children of any list nodes.
3
+ * @param nodes
4
+ * @returns
5
+ */
6
+ export const unwrapListStep = (nodes, context) => {
7
+ const listTypes = [context.schema.nodes.bulletList, context.schema.nodes.orderedList, context.schema.nodes.taskList];
8
+ return nodes.flatMap(node => {
9
+ if (listTypes.some(type => node.type === type)) {
10
+ const listItems = [];
11
+ node.forEach(listItem => {
12
+ listItems.push(...listItem.children);
13
+ });
14
+ return listItems;
15
+ }
16
+ return node;
17
+ });
18
+ };
@@ -1,3 +1,4 @@
1
+ import { expandToBlockRange } from '@atlaskit/editor-common/selection';
1
2
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
2
3
  import { getOutputNodes } from './transform-node-utils/transform';
3
4
  import { isListNode } from './transforms/utils';
@@ -13,14 +14,13 @@ export const transformNode = api =>
13
14
  return tr;
14
15
  }
15
16
  const {
16
- from,
17
- to,
18
- $from
19
- } = preservedSelection;
17
+ $from,
18
+ $to
19
+ } = expandToBlockRange(preservedSelection.$from, preservedSelection.$to);
20
20
  const selectedParent = $from.parent;
21
21
  let fragment = Fragment.empty;
22
22
  const isList = isListNode(selectedParent);
23
- const slice = tr.doc.slice(isList ? from - 1 : from, isList ? to + 1 : to);
23
+ const slice = tr.doc.slice(isList ? $from.pos - 1 : $from.pos, isList ? $to.pos + 1 : $to.pos);
24
24
  slice.content.forEach(node => {
25
25
  const outputNode = getOutputNodes({
26
26
  sourceNode: node,
@@ -31,7 +31,9 @@ export const transformNode = api =>
31
31
  fragment = fragment.append(Fragment.fromArray(outputNode));
32
32
  }
33
33
  });
34
- tr.replaceWith(isList ? preservedSelection.from - 1 : preservedSelection.from, preservedSelection.to, fragment);
34
+
35
+ // TODO: ED-12345 - selection is broken post transaction, to fix.
36
+ tr.replaceWith(isList ? $from.pos - 1 : $from.pos, $to.pos, fragment);
35
37
  return tr;
36
38
  };
37
39
  };
@@ -25,6 +25,7 @@ const styles = {
25
25
  };
26
26
  const DEFAULT_MENU_WIDTH = 230;
27
27
  const DRAG_HANDLE_OFFSET_PADDING = 5;
28
+ const FALLBACK_MENU_HEIGHT = 300;
28
29
  const PopupWithListeners = withReactEditorViewOuterListeners(Popup);
29
30
  const useConditionalBlockMenuEffect = conditionalHooksFactory(() => fg('platform_editor_toolbar_aifc_user_intent_fix'), ({
30
31
  api,
@@ -153,6 +154,15 @@ const BlockMenu = ({
153
154
  const targetHandleRef = editorView === null || editorView === void 0 ? void 0 : (_editorView$dom = editorView.dom) === null || _editorView$dom === void 0 ? void 0 : _editorView$dom.querySelector(DRAG_HANDLE_SELECTOR);
154
155
  const prevIsMenuOpenRef = useRef(false);
155
156
  const popupRef = useRef(undefined);
157
+ const [menuHeight, setMenuHeight] = React.useState(0);
158
+ const targetHandleHeightOffset = -((targetHandleRef === null || targetHandleRef === void 0 ? void 0 : targetHandleRef.clientHeight) || 0);
159
+ React.useLayoutEffect(() => {
160
+ var _popupRef$current;
161
+ if (!isMenuOpen) {
162
+ return;
163
+ }
164
+ setMenuHeight(((_popupRef$current = popupRef.current) === null || _popupRef$current === void 0 ? void 0 : _popupRef$current.clientHeight) || FALLBACK_MENU_HEIGHT);
165
+ }, [isMenuOpen]);
156
166
  const hasFocus = (_ref = (editorView === null || editorView === void 0 ? void 0 : editorView.hasFocus()) || document.activeElement === targetHandleRef || popupRef.current && (popupRef.current.contains(document.activeElement) || popupRef.current === document.activeElement)) !== null && _ref !== void 0 ? _ref : false;
157
167
  const selectedByShortcutOrDragHandle = !!isSelectedViaDragHandle || !!openedViaKeyboard;
158
168
 
@@ -218,8 +228,7 @@ const BlockMenu = ({
218
228
  fallbackComponent: null
219
229
  }, /*#__PURE__*/React.createElement(PopupWithListeners, {
220
230
  alignX: 'right',
221
- alignY: 'start' // respected when forcePlacement is true
222
- ,
231
+ alignY: 'start',
223
232
  handleClickOutside: closeMenu,
224
233
  handleEscapeKeydown: closeMenu,
225
234
  handleBackspaceDeleteKeydown: handleBackspaceDeleteKeydown,
@@ -229,16 +238,15 @@ const BlockMenu = ({
229
238
  target: targetHandleRef,
230
239
  zIndex: akEditorFloatingOverlapPanelZIndex,
231
240
  fitWidth: DEFAULT_MENU_WIDTH,
232
- forcePlacement: true,
233
- preventOverflow: true // disables forced horizontal placement when forcePlacement is on, so fitWidth controls flipping
234
- ,
241
+ fitHeight: menuHeight,
242
+ preventOverflow: true,
235
243
  stick: true,
244
+ offset: [DRAG_HANDLE_WIDTH + DRAG_HANDLE_OFFSET_PADDING, targetHandleHeightOffset],
236
245
  focusTrap: openedViaKeyboard ?
237
246
  // Only enable focus trap when opened via keyboard to make sure the focus is on the first focusable menu item
238
247
  {
239
248
  initialFocus: undefined
240
- } : undefined,
241
- offset: [DRAG_HANDLE_WIDTH + DRAG_HANDLE_OFFSET_PADDING, 0]
249
+ } : undefined
242
250
  }, /*#__PURE__*/React.createElement(BlockMenuContent, {
243
251
  api: api,
244
252
  setRef: setRef
@@ -0,0 +1,102 @@
1
+ import { Fragment } from '@atlaskit/editor-prosemirror/model';
2
+ var extractNestedLists = function extractNestedLists(node, listTypes, itemTypes) {
3
+ var items = [];
4
+ var _extract = function extract(currentNode) {
5
+ currentNode.forEach(function (child) {
6
+ // list item -> take content without nested lists, then recurse into nested lists
7
+ if (itemTypes.some(function (type) {
8
+ return child.type === type;
9
+ })) {
10
+ // Filter out nested list nodes from the list item's content
11
+ var contentWithoutNestedLists = [];
12
+ var nestedLists = [];
13
+ child.forEach(function (grandChild) {
14
+ if (listTypes.some(function (type) {
15
+ return grandChild.type === type;
16
+ })) {
17
+ // This is a nested list - collect it for later processing
18
+ nestedLists.push(grandChild);
19
+ } else {
20
+ // This is regular content (paragraph, etc.) - keep it
21
+ contentWithoutNestedLists.push(grandChild);
22
+ }
23
+ });
24
+
25
+ // Add the list item with only its non-list content
26
+ items.push(child.copy(Fragment.from(contentWithoutNestedLists)));
27
+
28
+ // Now process nested lists to maintain document order
29
+ nestedLists.forEach(function (nestedList) {
30
+ _extract(nestedList);
31
+ });
32
+ }
33
+ // lists -> keep operating
34
+ else if (listTypes.some(function (type) {
35
+ return child.type === type;
36
+ })) {
37
+ _extract(child);
38
+ }
39
+ });
40
+ };
41
+ _extract(node);
42
+ return items;
43
+ };
44
+
45
+ /**
46
+ * Given an array of nodes, returns an array with the flattened children of any list node
47
+ * to it's first ancestor list, maintaining document order.
48
+ *
49
+ * @example
50
+ * Input:
51
+ * - bulletList
52
+ * - listItem "A"
53
+ * - listItem "B"
54
+ * - bulletList
55
+ * - listItem "C"
56
+ * - listItem "D"
57
+ * - listItem "E"
58
+ *
59
+ * Output:
60
+ * - bulletList
61
+ * - listItem "A"
62
+ * - listItem "B"
63
+ * - listItem "C"
64
+ * - listItem "D"
65
+ * - listItem "E"
66
+ *
67
+ * @example
68
+ * Input (deeply nested):
69
+ * - bulletList
70
+ * - listItem "1"
71
+ * - bulletList
72
+ * - listItem "1.1"
73
+ * - bulletList
74
+ * - listItem "1.1.1"
75
+ * - listItem "1.2"
76
+ * - listItem "2"
77
+ *
78
+ * Output:
79
+ * - bulletList
80
+ * - listItem "1"
81
+ * - listItem "1.1"
82
+ * - listItem "1.1.1"
83
+ * - listItem "1.2"
84
+ * - listItem "2"
85
+ *
86
+ * @param nodes
87
+ * @param context
88
+ * @returns
89
+ *
90
+ * TODO: Lists with mixed types (e.g. bulletList with a taskItem) doesn't full flatten
91
+ */
92
+ export var flattenListStep = function flattenListStep(nodes, context) {
93
+ var listTypes = [context.schema.nodes.bulletList, context.schema.nodes.orderedList, context.schema.nodes.taskList];
94
+ return nodes.map(function (node) {
95
+ if (listTypes.some(function (type) {
96
+ return node.type === type;
97
+ })) {
98
+ return node.copy(Fragment.from(extractNestedLists(node, listTypes, [context.schema.nodes.listItem, context.schema.nodes.taskItem])));
99
+ }
100
+ return node;
101
+ });
102
+ };
@@ -1,5 +1,7 @@
1
+ import { flattenListStep } from './flattenListStep';
1
2
  import { stubStep } from './stubStep';
2
3
  import { NODE_CATEGORY_BY_TYPE, toNodeTypeValue } from './types';
4
+ import { unwrapListStep } from './unwrapListStep';
3
5
 
4
6
  // Exampled step for overrides:
5
7
  // - open Block menu on a paragraph, click 'Panel' in the Turn into'
@@ -27,7 +29,7 @@ var TRANSFORM_STEPS = {
27
29
  atomic: undefined,
28
30
  container: [stubStep],
29
31
  list: [stubStep],
30
- text: [stubStep]
32
+ text: [flattenListStep, unwrapListStep]
31
33
  },
32
34
  text: {
33
35
  atomic: undefined,
@@ -0,0 +1,21 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
+ /**
3
+ * Given an array of nodes, returns an array with the flattened children of any list nodes.
4
+ * @param nodes
5
+ * @returns
6
+ */
7
+ export var unwrapListStep = function unwrapListStep(nodes, context) {
8
+ var listTypes = [context.schema.nodes.bulletList, context.schema.nodes.orderedList, context.schema.nodes.taskList];
9
+ return nodes.flatMap(function (node) {
10
+ if (listTypes.some(function (type) {
11
+ return node.type === type;
12
+ })) {
13
+ var listItems = [];
14
+ node.forEach(function (listItem) {
15
+ listItems.push.apply(listItems, _toConsumableArray(listItem.children));
16
+ });
17
+ return listItems;
18
+ }
19
+ return node;
20
+ });
21
+ };
@@ -1,3 +1,4 @@
1
+ import { expandToBlockRange } from '@atlaskit/editor-common/selection';
1
2
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
2
3
  import { getOutputNodes } from './transform-node-utils/transform';
3
4
  import { isListNode } from './transforms/utils';
@@ -12,13 +13,13 @@ export var transformNode = function transformNode(api) {
12
13
  if (!preservedSelection) {
13
14
  return tr;
14
15
  }
15
- var from = preservedSelection.from,
16
- to = preservedSelection.to,
17
- $from = preservedSelection.$from;
16
+ var _expandToBlockRange = expandToBlockRange(preservedSelection.$from, preservedSelection.$to),
17
+ $from = _expandToBlockRange.$from,
18
+ $to = _expandToBlockRange.$to;
18
19
  var selectedParent = $from.parent;
19
20
  var fragment = Fragment.empty;
20
21
  var isList = isListNode(selectedParent);
21
- var slice = tr.doc.slice(isList ? from - 1 : from, isList ? to + 1 : to);
22
+ var slice = tr.doc.slice(isList ? $from.pos - 1 : $from.pos, isList ? $to.pos + 1 : $to.pos);
22
23
  slice.content.forEach(function (node) {
23
24
  var outputNode = getOutputNodes({
24
25
  sourceNode: node,
@@ -29,7 +30,9 @@ export var transformNode = function transformNode(api) {
29
30
  fragment = fragment.append(Fragment.fromArray(outputNode));
30
31
  }
31
32
  });
32
- tr.replaceWith(isList ? preservedSelection.from - 1 : preservedSelection.from, preservedSelection.to, fragment);
33
+
34
+ // TODO: ED-12345 - selection is broken post transaction, to fix.
35
+ tr.replaceWith(isList ? $from.pos - 1 : $from.pos, $to.pos, fragment);
33
36
  return tr;
34
37
  };
35
38
  }
@@ -1,4 +1,5 @@
1
1
  /* block-menu.tsx generated by @compiled/babel-plugin v0.38.1 */
2
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
3
  import "./block-menu.compiled.css";
3
4
  import { ax, ix } from "@compiled/react/runtime";
4
5
  import React, { useContext, useEffect, useRef } from 'react';
@@ -25,6 +26,7 @@ var styles = {
25
26
  };
26
27
  var DEFAULT_MENU_WIDTH = 230;
27
28
  var DRAG_HANDLE_OFFSET_PADDING = 5;
29
+ var FALLBACK_MENU_HEIGHT = 300;
28
30
  var PopupWithListeners = withReactEditorViewOuterListeners(Popup);
29
31
  var useConditionalBlockMenuEffect = conditionalHooksFactory(function () {
30
32
  return fg('platform_editor_toolbar_aifc_user_intent_fix');
@@ -155,6 +157,18 @@ var BlockMenu = function BlockMenu(_ref4) {
155
157
  var targetHandleRef = editorView === null || editorView === void 0 || (_editorView$dom = editorView.dom) === null || _editorView$dom === void 0 ? void 0 : _editorView$dom.querySelector(DRAG_HANDLE_SELECTOR);
156
158
  var prevIsMenuOpenRef = useRef(false);
157
159
  var popupRef = useRef(undefined);
160
+ var _React$useState = React.useState(0),
161
+ _React$useState2 = _slicedToArray(_React$useState, 2),
162
+ menuHeight = _React$useState2[0],
163
+ setMenuHeight = _React$useState2[1];
164
+ var targetHandleHeightOffset = -((targetHandleRef === null || targetHandleRef === void 0 ? void 0 : targetHandleRef.clientHeight) || 0);
165
+ React.useLayoutEffect(function () {
166
+ var _popupRef$current;
167
+ if (!isMenuOpen) {
168
+ return;
169
+ }
170
+ setMenuHeight(((_popupRef$current = popupRef.current) === null || _popupRef$current === void 0 ? void 0 : _popupRef$current.clientHeight) || FALLBACK_MENU_HEIGHT);
171
+ }, [isMenuOpen]);
158
172
  var hasFocus = (_ref5 = (editorView === null || editorView === void 0 ? void 0 : editorView.hasFocus()) || document.activeElement === targetHandleRef || popupRef.current && (popupRef.current.contains(document.activeElement) || popupRef.current === document.activeElement)) !== null && _ref5 !== void 0 ? _ref5 : false;
159
173
  var selectedByShortcutOrDragHandle = !!isSelectedViaDragHandle || !!openedViaKeyboard;
160
174
 
@@ -218,8 +232,7 @@ var BlockMenu = function BlockMenu(_ref4) {
218
232
  fallbackComponent: null
219
233
  }, /*#__PURE__*/React.createElement(PopupWithListeners, {
220
234
  alignX: 'right',
221
- alignY: 'start' // respected when forcePlacement is true
222
- ,
235
+ alignY: 'start',
223
236
  handleClickOutside: closeMenu,
224
237
  handleEscapeKeydown: closeMenu,
225
238
  handleBackspaceDeleteKeydown: handleBackspaceDeleteKeydown,
@@ -229,16 +242,15 @@ var BlockMenu = function BlockMenu(_ref4) {
229
242
  target: targetHandleRef,
230
243
  zIndex: akEditorFloatingOverlapPanelZIndex,
231
244
  fitWidth: DEFAULT_MENU_WIDTH,
232
- forcePlacement: true,
233
- preventOverflow: true // disables forced horizontal placement when forcePlacement is on, so fitWidth controls flipping
234
- ,
245
+ fitHeight: menuHeight,
246
+ preventOverflow: true,
235
247
  stick: true,
248
+ offset: [DRAG_HANDLE_WIDTH + DRAG_HANDLE_OFFSET_PADDING, targetHandleHeightOffset],
236
249
  focusTrap: openedViaKeyboard ?
237
250
  // Only enable focus trap when opened via keyboard to make sure the focus is on the first focusable menu item
238
251
  {
239
252
  initialFocus: undefined
240
- } : undefined,
241
- offset: [DRAG_HANDLE_WIDTH + DRAG_HANDLE_OFFSET_PADDING, 0]
253
+ } : undefined
242
254
  }, /*#__PURE__*/React.createElement(BlockMenuContent, {
243
255
  api: api,
244
256
  setRef: setRef
@@ -0,0 +1,49 @@
1
+ import type { TransformStep } from './types';
2
+ /**
3
+ * Given an array of nodes, returns an array with the flattened children of any list node
4
+ * to it's first ancestor list, maintaining document order.
5
+ *
6
+ * @example
7
+ * Input:
8
+ * - bulletList
9
+ * - listItem "A"
10
+ * - listItem "B"
11
+ * - bulletList
12
+ * - listItem "C"
13
+ * - listItem "D"
14
+ * - listItem "E"
15
+ *
16
+ * Output:
17
+ * - bulletList
18
+ * - listItem "A"
19
+ * - listItem "B"
20
+ * - listItem "C"
21
+ * - listItem "D"
22
+ * - listItem "E"
23
+ *
24
+ * @example
25
+ * Input (deeply nested):
26
+ * - bulletList
27
+ * - listItem "1"
28
+ * - bulletList
29
+ * - listItem "1.1"
30
+ * - bulletList
31
+ * - listItem "1.1.1"
32
+ * - listItem "1.2"
33
+ * - listItem "2"
34
+ *
35
+ * Output:
36
+ * - bulletList
37
+ * - listItem "1"
38
+ * - listItem "1.1"
39
+ * - listItem "1.1.1"
40
+ * - listItem "1.2"
41
+ * - listItem "2"
42
+ *
43
+ * @param nodes
44
+ * @param context
45
+ * @returns
46
+ *
47
+ * TODO: Lists with mixed types (e.g. bulletList with a taskItem) doesn't full flatten
48
+ */
49
+ export declare const flattenListStep: TransformStep;
@@ -1,4 +1,4 @@
1
- import type { Node as PMNode, NodeType, Schema } from '@atlaskit/editor-prosemirror/model';
1
+ import { type Node as PMNode, type NodeType, type Schema } from '@atlaskit/editor-prosemirror/model';
2
2
  interface GetOutputNodesArgs {
3
3
  schema: Schema;
4
4
  sourceNode: PMNode;
@@ -0,0 +1,7 @@
1
+ import type { TransformStep } from './types';
2
+ /**
3
+ * Given an array of nodes, returns an array with the flattened children of any list nodes.
4
+ * @param nodes
5
+ * @returns
6
+ */
7
+ export declare const unwrapListStep: TransformStep;
@@ -1,7 +1,7 @@
1
1
  import type { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
2
  import type { TransformContext } from '@atlaskit/editor-common/transforms';
3
3
  import type { Transaction } from '@atlaskit/editor-prosemirror/state';
4
- export type FormatNodeTargetType = 'heading1' | 'heading2' | 'heading3' | 'heading4' | 'heading5' | 'heading6' | 'paragraph' | 'blockquote' | 'expand' | 'layoutSection' | 'panel' | 'codeBlock' | 'bulletList' | 'orderedList' | 'taskList';
4
+ export type FormatNodeTargetType = 'heading1' | 'heading2' | 'heading3' | 'heading4' | 'heading5' | 'heading6' | 'paragraph' | 'blockquote' | 'expand' | 'layoutSection' | 'panel' | 'codeBlock' | 'bulletList' | 'orderedList' | 'taskList' | 'decisionList';
5
5
  export type TransfromNodeTargetType = FormatNodeTargetType;
6
6
  export type FormatNodeAnalyticsAttrs = {
7
7
  inputMethod: INPUT_METHOD.BLOCK_MENU;
@@ -0,0 +1,49 @@
1
+ import type { TransformStep } from './types';
2
+ /**
3
+ * Given an array of nodes, returns an array with the flattened children of any list node
4
+ * to it's first ancestor list, maintaining document order.
5
+ *
6
+ * @example
7
+ * Input:
8
+ * - bulletList
9
+ * - listItem "A"
10
+ * - listItem "B"
11
+ * - bulletList
12
+ * - listItem "C"
13
+ * - listItem "D"
14
+ * - listItem "E"
15
+ *
16
+ * Output:
17
+ * - bulletList
18
+ * - listItem "A"
19
+ * - listItem "B"
20
+ * - listItem "C"
21
+ * - listItem "D"
22
+ * - listItem "E"
23
+ *
24
+ * @example
25
+ * Input (deeply nested):
26
+ * - bulletList
27
+ * - listItem "1"
28
+ * - bulletList
29
+ * - listItem "1.1"
30
+ * - bulletList
31
+ * - listItem "1.1.1"
32
+ * - listItem "1.2"
33
+ * - listItem "2"
34
+ *
35
+ * Output:
36
+ * - bulletList
37
+ * - listItem "1"
38
+ * - listItem "1.1"
39
+ * - listItem "1.1.1"
40
+ * - listItem "1.2"
41
+ * - listItem "2"
42
+ *
43
+ * @param nodes
44
+ * @param context
45
+ * @returns
46
+ *
47
+ * TODO: Lists with mixed types (e.g. bulletList with a taskItem) doesn't full flatten
48
+ */
49
+ export declare const flattenListStep: TransformStep;
@@ -1,4 +1,4 @@
1
- import type { Node as PMNode, NodeType, Schema } from '@atlaskit/editor-prosemirror/model';
1
+ import { type Node as PMNode, type NodeType, type Schema } from '@atlaskit/editor-prosemirror/model';
2
2
  interface GetOutputNodesArgs {
3
3
  schema: Schema;
4
4
  sourceNode: PMNode;
@@ -0,0 +1,7 @@
1
+ import type { TransformStep } from './types';
2
+ /**
3
+ * Given an array of nodes, returns an array with the flattened children of any list nodes.
4
+ * @param nodes
5
+ * @returns
6
+ */
7
+ export declare const unwrapListStep: TransformStep;
@@ -1,7 +1,7 @@
1
1
  import type { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
2
  import type { TransformContext } from '@atlaskit/editor-common/transforms';
3
3
  import type { Transaction } from '@atlaskit/editor-prosemirror/state';
4
- export type FormatNodeTargetType = 'heading1' | 'heading2' | 'heading3' | 'heading4' | 'heading5' | 'heading6' | 'paragraph' | 'blockquote' | 'expand' | 'layoutSection' | 'panel' | 'codeBlock' | 'bulletList' | 'orderedList' | 'taskList';
4
+ export type FormatNodeTargetType = 'heading1' | 'heading2' | 'heading3' | 'heading4' | 'heading5' | 'heading6' | 'paragraph' | 'blockquote' | 'expand' | 'layoutSection' | 'panel' | 'codeBlock' | 'bulletList' | 'orderedList' | 'taskList' | 'decisionList';
5
5
  export type TransfromNodeTargetType = FormatNodeTargetType;
6
6
  export type FormatNodeAnalyticsAttrs = {
7
7
  inputMethod: INPUT_METHOD.BLOCK_MENU;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-block-menu",
3
- "version": "5.1.3",
3
+ "version": "5.1.4",
4
4
  "description": "BlockMenu plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -44,12 +44,12 @@
44
44
  "@atlaskit/platform-feature-flags": "^1.1.0",
45
45
  "@atlaskit/platform-feature-flags-react": "^0.4.0",
46
46
  "@atlaskit/primitives": "^16.4.0",
47
- "@atlaskit/tmp-editor-statsig": "^14.4.0",
47
+ "@atlaskit/tmp-editor-statsig": "^14.5.0",
48
48
  "@atlaskit/tokens": "^8.4.0",
49
49
  "@babel/runtime": "^7.0.0"
50
50
  },
51
51
  "peerDependencies": {
52
- "@atlaskit/editor-common": "^110.37.0",
52
+ "@atlaskit/editor-common": "^110.38.0",
53
53
  "react": "^18.2.0",
54
54
  "react-intl-next": "npm:react-intl@^5.18.1"
55
55
  },