@atlaskit/editor-plugin-block-menu 5.2.5 → 5.2.7

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 (32) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/afm-cc/tsconfig.json +3 -0
  3. package/afm-jira/tsconfig.json +3 -0
  4. package/afm-products/tsconfig.json +3 -0
  5. package/dist/cjs/editor-commands/transform-node-utils/steps/wrapBlockquoteToDecisionListStep.js +91 -0
  6. package/dist/cjs/editor-commands/transform-node-utils/steps/wrapMixedContentStep.js +1 -5
  7. package/dist/cjs/editor-commands/transform-node-utils/transform.js +3 -1
  8. package/dist/cjs/editor-commands/transform-node-utils/utils.js +13 -1
  9. package/dist/cjs/ui/block-menu-provider.js +0 -11
  10. package/dist/cjs/ui/move-down.js +4 -8
  11. package/dist/cjs/ui/move-up.js +5 -9
  12. package/dist/es2019/editor-commands/transform-node-utils/steps/wrapBlockquoteToDecisionListStep.js +88 -0
  13. package/dist/es2019/editor-commands/transform-node-utils/steps/wrapMixedContentStep.js +1 -5
  14. package/dist/es2019/editor-commands/transform-node-utils/transform.js +3 -1
  15. package/dist/es2019/editor-commands/transform-node-utils/utils.js +12 -0
  16. package/dist/es2019/ui/block-menu-provider.js +0 -11
  17. package/dist/es2019/ui/move-down.js +4 -8
  18. package/dist/es2019/ui/move-up.js +5 -9
  19. package/dist/esm/editor-commands/transform-node-utils/steps/wrapBlockquoteToDecisionListStep.js +86 -0
  20. package/dist/esm/editor-commands/transform-node-utils/steps/wrapMixedContentStep.js +1 -5
  21. package/dist/esm/editor-commands/transform-node-utils/transform.js +3 -1
  22. package/dist/esm/editor-commands/transform-node-utils/utils.js +12 -0
  23. package/dist/esm/ui/block-menu-provider.js +0 -11
  24. package/dist/esm/ui/move-down.js +4 -8
  25. package/dist/esm/ui/move-up.js +5 -9
  26. package/dist/types/editor-commands/transform-node-utils/steps/wrapBlockquoteToDecisionListStep.d.ts +15 -0
  27. package/dist/types/editor-commands/transform-node-utils/utils.d.ts +2 -2
  28. package/dist/types/ui/block-menu-provider.d.ts +0 -5
  29. package/dist/types-ts4.5/editor-commands/transform-node-utils/steps/wrapBlockquoteToDecisionListStep.d.ts +15 -0
  30. package/dist/types-ts4.5/editor-commands/transform-node-utils/utils.d.ts +2 -2
  31. package/dist/types-ts4.5/ui/block-menu-provider.d.ts +0 -5
  32. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @atlaskit/editor-plugin-block-menu
2
2
 
3
+ ## 5.2.7
4
+
5
+ ### Patch Changes
6
+
7
+ - [`b2e5262017fa8`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/b2e5262017fa8) -
8
+ Editor-2676: Remove moveFocusTo in block menu provider
9
+ - Updated dependencies
10
+
11
+ ## 5.2.6
12
+
13
+ ### Patch Changes
14
+
15
+ - [`fa50da8ee6860`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/fa50da8ee6860) -
16
+ EDITOR-3879 [multi-select] Detect nodes from multi-selection
17
+ - Updated dependencies
18
+
3
19
  ## 5.2.5
4
20
 
5
21
  ### Patch Changes
@@ -39,6 +39,9 @@
39
39
  {
40
40
  "path": "../../editor-plugin-user-intent/afm-cc/tsconfig.json"
41
41
  },
42
+ {
43
+ "path": "../../editor-prosemirror/afm-cc/tsconfig.json"
44
+ },
42
45
  {
43
46
  "path": "../../editor-shared-styles/afm-cc/tsconfig.json"
44
47
  },
@@ -39,6 +39,9 @@
39
39
  {
40
40
  "path": "../../editor-plugin-user-intent/afm-jira/tsconfig.json"
41
41
  },
42
+ {
43
+ "path": "../../editor-prosemirror/afm-jira/tsconfig.json"
44
+ },
42
45
  {
43
46
  "path": "../../editor-shared-styles/afm-jira/tsconfig.json"
44
47
  },
@@ -39,6 +39,9 @@
39
39
  {
40
40
  "path": "../../editor-plugin-user-intent/afm-products/tsconfig.json"
41
41
  },
42
+ {
43
+ "path": "../../editor-prosemirror/afm-products/tsconfig.json"
44
+ },
42
45
  {
43
46
  "path": "../../editor-shared-styles/afm-products/tsconfig.json"
44
47
  },
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.wrapBlockquoteToDecisionListStep = void 0;
7
+ var _types = require("../types");
8
+ /**
9
+ * Determines if a node is a text node (heading or paragraph).
10
+ * Only text nodes should have their inline content extracted for decisionItem.
11
+ * All other nodes should break out.
12
+ */
13
+ var isTextNode = function isTextNode(node) {
14
+ var category = _types.NODE_CATEGORY_BY_TYPE[node.type.name];
15
+ return category === 'text';
16
+ };
17
+
18
+ /**
19
+ * Creates a decisionItem with the given inline content.
20
+ */
21
+ var createDecisionItem = function createDecisionItem(inlineContent, schema) {
22
+ var decisionItemType = schema.nodes.decisionItem;
23
+ if (!decisionItemType) {
24
+ return null;
25
+ }
26
+ var canContentBeWrappedInDecisionItem = decisionItemType.validContent(inlineContent);
27
+
28
+ // Check if the content is valid for decisionItem
29
+ if (!canContentBeWrappedInDecisionItem) {
30
+ return null;
31
+ }
32
+ return decisionItemType.createAndFill({}, inlineContent);
33
+ };
34
+
35
+ /**
36
+ * Creates a decisionList containing the given decisionItems.
37
+ */
38
+ var createDecisionListWithItems = function createDecisionListWithItems(decisionItems, schema) {
39
+ var decisionListType = schema.nodes.decisionList;
40
+ if (!decisionListType || decisionItems.length === 0) {
41
+ return null;
42
+ }
43
+ return decisionListType.createAndFill({}, decisionItems);
44
+ };
45
+
46
+ /**
47
+ * Wraps blockquote content into decisionList:
48
+ * - Text nodes (paragraph, heading) have their inline content extracted and wrapped in decisionItems
49
+ * - Consecutive text nodes are grouped into a single decisionList with multiple decisionItems
50
+ * - All other nodes break out (lists, code blocks, media, tables, macros, containers, etc.)
51
+ *
52
+ * The logic follows the transform rules:
53
+ * - Only flatten text nodes into decisionItem (since that's the intent - converting text to decisions)
54
+ * - Structures that can't be represented in a decisionItem should break out unchanged
55
+ * - When a break-out node is encountered, flush accumulated decisionItems into a decisionList
56
+ *
57
+ * Example: blockquote(p('a'), p('b'), ul(...), p('c')) → [decisionList(decisionItem('a'), decisionItem('b')), ul(...), decisionList(decisionItem('c'))]
58
+ */
59
+ var wrapBlockquoteToDecisionListStep = exports.wrapBlockquoteToDecisionListStep = function wrapBlockquoteToDecisionListStep(nodes, context) {
60
+ var schema = context.schema;
61
+ var decisionItemType = schema.nodes.decisionItem;
62
+ if (!decisionItemType) {
63
+ return nodes;
64
+ }
65
+ var result = [];
66
+ var currentDecisionItems = [];
67
+ var flushCurrentDecisionList = function flushCurrentDecisionList() {
68
+ if (currentDecisionItems.length > 0) {
69
+ var decisionList = createDecisionListWithItems(currentDecisionItems, schema);
70
+ if (decisionList) {
71
+ result.push(decisionList);
72
+ }
73
+ currentDecisionItems = [];
74
+ }
75
+ };
76
+ nodes.forEach(function (node) {
77
+ var decisionItem = isTextNode(node) ? createDecisionItem(node.content, schema) : null;
78
+ if (decisionItem) {
79
+ // Accumulate consecutive decisionItems
80
+ currentDecisionItems.push(decisionItem);
81
+ } else {
82
+ // Content can't be wrapped in decisionItem - break out the node
83
+ flushCurrentDecisionList();
84
+ result.push(node);
85
+ }
86
+ });
87
+
88
+ // Flush any remaining decisionItems
89
+ flushCurrentDecisionList();
90
+ return result.length > 0 ? result : nodes;
91
+ };
@@ -45,11 +45,7 @@ var canWrapInTarget = function canWrapInTarget(node, targetNodeType, targetNodeT
45
45
  }
46
46
 
47
47
  // Use the schema to determine if this node can be contained in the target
48
- try {
49
- return targetNodeType.validContent(_model.Fragment.from(node));
50
- } catch (_unused) {
51
- return false;
52
- }
48
+ return targetNodeType.validContent(_model.Fragment.from(node));
53
49
  };
54
50
 
55
51
  /**
@@ -12,6 +12,7 @@ var _listToDecisionListStep = require("./steps/listToDecisionListStep");
12
12
  var _listToListStep = require("./steps/listToListStep");
13
13
  var _unwrapLayoutStep = require("./steps/unwrapLayoutStep");
14
14
  var _unwrapListStep = require("./steps/unwrapListStep");
15
+ var _wrapBlockquoteToDecisionListStep = require("./steps/wrapBlockquoteToDecisionListStep");
15
16
  var _wrapMixedContentStep = require("./steps/wrapMixedContentStep");
16
17
  var _stubStep = require("./stubStep");
17
18
  var _types = require("./types");
@@ -83,7 +84,8 @@ var TRANSFORM_STEPS_OVERRIDE = {
83
84
  expand: [_wrapStep.wrapStep],
84
85
  nestedExpand: [_wrapStep.wrapStep],
85
86
  layoutSection: [_wrapIntoLayoutStep.wrapIntoLayoutStep],
86
- codeBlock: [_unwrapStep.unwrapStep, _flattenStep.flattenStep, _wrapStep.wrapStep]
87
+ codeBlock: [_unwrapStep.unwrapStep, _flattenStep.flattenStep, _wrapStep.wrapStep],
88
+ decisionList: [_unwrapStep.unwrapStep, _wrapBlockquoteToDecisionListStep.wrapBlockquoteToDecisionListStep]
87
89
  },
88
90
  layoutSection: {
89
91
  blockquote: [_unwrapLayoutStep.unwrapLayoutStep, _wrapStep.wrapStep],
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.isListType = exports.getTargetNodeTypeNameInContext = exports.getSelectedNode = exports.expandSelectionToBlockRange = void 0;
6
+ exports.isListType = exports.getTargetNodeTypeNameInContext = exports.getSelectedNode = exports.getBlockNodesInRange = exports.expandSelectionToBlockRange = void 0;
7
7
  var _selection = require("@atlaskit/editor-common/selection");
8
8
  var _state = require("@atlaskit/editor-prosemirror/state");
9
9
  var _utils = require("@atlaskit/editor-prosemirror/utils");
@@ -95,4 +95,16 @@ var isListType = exports.isListType = function isListType(node, schema) {
95
95
  return lists.some(function (list) {
96
96
  return list === node.type;
97
97
  });
98
+ };
99
+ var getBlockNodesInRange = exports.getBlockNodesInRange = function getBlockNodesInRange(range) {
100
+ if (range.endIndex - range.startIndex <= 1) {
101
+ return [range.parent.child(range.startIndex)];
102
+ }
103
+ var blockNodes = [];
104
+ for (var i = range.startIndex; i < range.endIndex; i++) {
105
+ if (range.parent.child(i).isBlock) {
106
+ blockNodes.push(range.parent.child(i));
107
+ }
108
+ }
109
+ return blockNodes;
98
110
  };
@@ -9,7 +9,6 @@ var _react = _interopRequireWildcard(require("react"));
9
9
  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); }
10
10
  var BlockMenuContext = /*#__PURE__*/(0, _react.createContext)({
11
11
  onDropdownOpenChanged: function onDropdownOpenChanged() {},
12
- moveFocusTo: function moveFocusTo() {},
13
12
  moveDownRef: /*#__PURE__*/_react.default.createRef(),
14
13
  moveUpRef: /*#__PURE__*/_react.default.createRef()
15
14
  });
@@ -37,19 +36,9 @@ var BlockMenuProvider = exports.BlockMenuProvider = function BlockMenuProvider(_
37
36
  }, 1);
38
37
  }
39
38
  }, [api]);
40
- var moveFocusTo = (0, _react.useCallback)(function (direction) {
41
- if (direction === 'moveUp') {
42
- var _moveUpRef$current;
43
- (_moveUpRef$current = moveUpRef.current) === null || _moveUpRef$current === void 0 || _moveUpRef$current.focus();
44
- } else if (direction === 'moveDown') {
45
- var _moveDownRef$current;
46
- (_moveDownRef$current = moveDownRef.current) === null || _moveDownRef$current === void 0 || _moveDownRef$current.focus();
47
- }
48
- }, []);
49
39
  return /*#__PURE__*/_react.default.createElement(BlockMenuContext.Provider, {
50
40
  value: {
51
41
  onDropdownOpenChanged: onDropdownOpenChanged,
52
- moveFocusTo: moveFocusTo,
53
42
  moveDownRef: moveDownRef,
54
43
  moveUpRef: moveUpRef
55
44
  }
@@ -22,7 +22,7 @@ var MoveDownDropdownItemContent = function MoveDownDropdownItemContent(_ref) {
22
22
  var _useIntl = (0, _reactIntlNext.useIntl)(),
23
23
  formatMessage = _useIntl.formatMessage;
24
24
  var _useBlockMenu = (0, _blockMenuProvider.useBlockMenu)(),
25
- moveFocusTo = _useBlockMenu.moveFocusTo,
25
+ moveUpRef = _useBlockMenu.moveUpRef,
26
26
  moveDownRef = _useBlockMenu.moveDownRef;
27
27
  var _useSharedPluginState = (0, _hooks.useSharedPluginStateWithSelector)(api, ['blockControls'], function (_ref2) {
28
28
  var _blockControlsState$b;
@@ -33,14 +33,10 @@ var MoveDownDropdownItemContent = function MoveDownDropdownItemContent(_ref) {
33
33
  }),
34
34
  canMoveDown = _useSharedPluginState.canMoveDown;
35
35
  (0, _react.useEffect)(function () {
36
- var moveDownElement = moveDownRef.current;
37
- if (!moveDownElement) {
38
- return;
36
+ if (!canMoveDown && moveDownRef.current && moveDownRef.current === document.activeElement && moveUpRef.current) {
37
+ moveUpRef.current.focus();
39
38
  }
40
- if (!canMoveDown && moveDownElement === document.activeElement) {
41
- moveFocusTo('moveUp');
42
- }
43
- }, [canMoveDown, moveFocusTo, moveDownRef]);
39
+ }, [canMoveDown, moveUpRef, moveDownRef]);
44
40
  var handleClick = function handleClick() {
45
41
  api === null || api === void 0 || api.core.actions.execute(function (_ref3) {
46
42
  var _api$analytics, _api$blockControls;
@@ -22,8 +22,8 @@ var MoveUpDropdownItemContent = function MoveUpDropdownItemContent(_ref) {
22
22
  var _useIntl = (0, _reactIntlNext.useIntl)(),
23
23
  formatMessage = _useIntl.formatMessage;
24
24
  var _useBlockMenu = (0, _blockMenuProvider.useBlockMenu)(),
25
- moveFocusTo = _useBlockMenu.moveFocusTo,
26
- moveUpRef = _useBlockMenu.moveUpRef;
25
+ moveUpRef = _useBlockMenu.moveUpRef,
26
+ moveDownRef = _useBlockMenu.moveDownRef;
27
27
  var _useSharedPluginState = (0, _hooks.useSharedPluginStateWithSelector)(api, ['blockControls'], function (_ref2) {
28
28
  var _blockControlsState$b;
29
29
  var blockControlsState = _ref2.blockControlsState;
@@ -33,14 +33,10 @@ var MoveUpDropdownItemContent = function MoveUpDropdownItemContent(_ref) {
33
33
  }),
34
34
  canMoveUp = _useSharedPluginState.canMoveUp;
35
35
  (0, _react.useEffect)(function () {
36
- var moveUpElement = moveUpRef.current;
37
- if (!moveUpElement) {
38
- return;
36
+ if (!canMoveUp && moveUpRef.current && moveUpRef.current === document.activeElement && moveDownRef.current) {
37
+ moveDownRef.current.focus();
39
38
  }
40
- if (!canMoveUp && moveUpElement === document.activeElement) {
41
- moveFocusTo('moveDown');
42
- }
43
- }, [canMoveUp, moveFocusTo, moveUpRef]);
39
+ }, [canMoveUp, moveDownRef, moveUpRef]);
44
40
  var handleClick = function handleClick() {
45
41
  api === null || api === void 0 || api.core.actions.execute(function (_ref3) {
46
42
  var _api$analytics, _api$blockControls;
@@ -0,0 +1,88 @@
1
+ import { NODE_CATEGORY_BY_TYPE } from '../types';
2
+
3
+ /**
4
+ * Determines if a node is a text node (heading or paragraph).
5
+ * Only text nodes should have their inline content extracted for decisionItem.
6
+ * All other nodes should break out.
7
+ */
8
+ const isTextNode = node => {
9
+ const category = NODE_CATEGORY_BY_TYPE[node.type.name];
10
+ return category === 'text';
11
+ };
12
+
13
+ /**
14
+ * Creates a decisionItem with the given inline content.
15
+ */
16
+ const createDecisionItem = (inlineContent, schema) => {
17
+ const decisionItemType = schema.nodes.decisionItem;
18
+ if (!decisionItemType) {
19
+ return null;
20
+ }
21
+ const canContentBeWrappedInDecisionItem = decisionItemType.validContent(inlineContent);
22
+
23
+ // Check if the content is valid for decisionItem
24
+ if (!canContentBeWrappedInDecisionItem) {
25
+ return null;
26
+ }
27
+ return decisionItemType.createAndFill({}, inlineContent);
28
+ };
29
+
30
+ /**
31
+ * Creates a decisionList containing the given decisionItems.
32
+ */
33
+ const createDecisionListWithItems = (decisionItems, schema) => {
34
+ const decisionListType = schema.nodes.decisionList;
35
+ if (!decisionListType || decisionItems.length === 0) {
36
+ return null;
37
+ }
38
+ return decisionListType.createAndFill({}, decisionItems);
39
+ };
40
+
41
+ /**
42
+ * Wraps blockquote content into decisionList:
43
+ * - Text nodes (paragraph, heading) have their inline content extracted and wrapped in decisionItems
44
+ * - Consecutive text nodes are grouped into a single decisionList with multiple decisionItems
45
+ * - All other nodes break out (lists, code blocks, media, tables, macros, containers, etc.)
46
+ *
47
+ * The logic follows the transform rules:
48
+ * - Only flatten text nodes into decisionItem (since that's the intent - converting text to decisions)
49
+ * - Structures that can't be represented in a decisionItem should break out unchanged
50
+ * - When a break-out node is encountered, flush accumulated decisionItems into a decisionList
51
+ *
52
+ * Example: blockquote(p('a'), p('b'), ul(...), p('c')) → [decisionList(decisionItem('a'), decisionItem('b')), ul(...), decisionList(decisionItem('c'))]
53
+ */
54
+ export const wrapBlockquoteToDecisionListStep = (nodes, context) => {
55
+ const {
56
+ schema
57
+ } = context;
58
+ const decisionItemType = schema.nodes.decisionItem;
59
+ if (!decisionItemType) {
60
+ return nodes;
61
+ }
62
+ const result = [];
63
+ let currentDecisionItems = [];
64
+ const flushCurrentDecisionList = () => {
65
+ if (currentDecisionItems.length > 0) {
66
+ const decisionList = createDecisionListWithItems(currentDecisionItems, schema);
67
+ if (decisionList) {
68
+ result.push(decisionList);
69
+ }
70
+ currentDecisionItems = [];
71
+ }
72
+ };
73
+ nodes.forEach(node => {
74
+ const decisionItem = isTextNode(node) ? createDecisionItem(node.content, schema) : null;
75
+ if (decisionItem) {
76
+ // Accumulate consecutive decisionItems
77
+ currentDecisionItems.push(decisionItem);
78
+ } else {
79
+ // Content can't be wrapped in decisionItem - break out the node
80
+ flushCurrentDecisionList();
81
+ result.push(node);
82
+ }
83
+ });
84
+
85
+ // Flush any remaining decisionItems
86
+ flushCurrentDecisionList();
87
+ return result.length > 0 ? result : nodes;
88
+ };
@@ -40,11 +40,7 @@ const canWrapInTarget = (node, targetNodeType, targetNodeTypeName) => {
40
40
  }
41
41
 
42
42
  // Use the schema to determine if this node can be contained in the target
43
- try {
44
- return targetNodeType.validContent(Fragment.from(node));
45
- } catch {
46
- return false;
47
- }
43
+ return targetNodeType.validContent(Fragment.from(node));
48
44
  };
49
45
 
50
46
  /**
@@ -6,6 +6,7 @@ import { listToDecisionListStep } from './steps/listToDecisionListStep';
6
6
  import { listToListStep } from './steps/listToListStep';
7
7
  import { unwrapLayoutStep } from './steps/unwrapLayoutStep';
8
8
  import { unwrapListStep } from './steps/unwrapListStep';
9
+ import { wrapBlockquoteToDecisionListStep } from './steps/wrapBlockquoteToDecisionListStep';
9
10
  import { wrapMixedContentStep } from './steps/wrapMixedContentStep';
10
11
  import { stubStep } from './stubStep';
11
12
  import { NODE_CATEGORY_BY_TYPE, toNodeTypeValue } from './types';
@@ -78,7 +79,8 @@ const TRANSFORM_STEPS_OVERRIDE = {
78
79
  expand: [wrapStep],
79
80
  nestedExpand: [wrapStep],
80
81
  layoutSection: [wrapIntoLayoutStep],
81
- codeBlock: [unwrapStep, flattenStep, wrapStep]
82
+ codeBlock: [unwrapStep, flattenStep, wrapStep],
83
+ decisionList: [unwrapStep, wrapBlockquoteToDecisionListStep]
82
84
  },
83
85
  layoutSection: {
84
86
  blockquote: [unwrapLayoutStep, wrapStep],
@@ -90,4 +90,16 @@ export const expandSelectionToBlockRange = (selection, schema) => {
90
90
  export const isListType = (node, schema) => {
91
91
  const lists = [schema.nodes.taskList, schema.nodes.bulletList, schema.nodes.orderedList];
92
92
  return lists.some(list => list === node.type);
93
+ };
94
+ export const getBlockNodesInRange = range => {
95
+ if (range.endIndex - range.startIndex <= 1) {
96
+ return [range.parent.child(range.startIndex)];
97
+ }
98
+ const blockNodes = [];
99
+ for (let i = range.startIndex; i < range.endIndex; i++) {
100
+ if (range.parent.child(i).isBlock) {
101
+ blockNodes.push(range.parent.child(i));
102
+ }
103
+ }
104
+ return blockNodes;
93
105
  };
@@ -1,7 +1,6 @@
1
1
  import React, { useCallback, createContext, useContext, useRef } from 'react';
2
2
  const BlockMenuContext = /*#__PURE__*/createContext({
3
3
  onDropdownOpenChanged: () => {},
4
- moveFocusTo: () => {},
5
4
  moveDownRef: /*#__PURE__*/React.createRef(),
6
5
  moveUpRef: /*#__PURE__*/React.createRef()
7
6
  });
@@ -28,19 +27,9 @@ export const BlockMenuProvider = ({
28
27
  }), 1);
29
28
  }
30
29
  }, [api]);
31
- const moveFocusTo = useCallback(direction => {
32
- if (direction === 'moveUp') {
33
- var _moveUpRef$current;
34
- (_moveUpRef$current = moveUpRef.current) === null || _moveUpRef$current === void 0 ? void 0 : _moveUpRef$current.focus();
35
- } else if (direction === 'moveDown') {
36
- var _moveDownRef$current;
37
- (_moveDownRef$current = moveDownRef.current) === null || _moveDownRef$current === void 0 ? void 0 : _moveDownRef$current.focus();
38
- }
39
- }, []);
40
30
  return /*#__PURE__*/React.createElement(BlockMenuContext.Provider, {
41
31
  value: {
42
32
  onDropdownOpenChanged,
43
- moveFocusTo,
44
33
  moveDownRef,
45
34
  moveUpRef
46
35
  }
@@ -15,7 +15,7 @@ const MoveDownDropdownItemContent = ({
15
15
  formatMessage
16
16
  } = useIntl();
17
17
  const {
18
- moveFocusTo,
18
+ moveUpRef,
19
19
  moveDownRef
20
20
  } = useBlockMenu();
21
21
  const {
@@ -29,14 +29,10 @@ const MoveDownDropdownItemContent = ({
29
29
  };
30
30
  });
31
31
  useEffect(() => {
32
- const moveDownElement = moveDownRef.current;
33
- if (!moveDownElement) {
34
- return;
32
+ if (!canMoveDown && moveDownRef.current && moveDownRef.current === document.activeElement && moveUpRef.current) {
33
+ moveUpRef.current.focus();
35
34
  }
36
- if (!canMoveDown && moveDownElement === document.activeElement) {
37
- moveFocusTo('moveUp');
38
- }
39
- }, [canMoveDown, moveFocusTo, moveDownRef]);
35
+ }, [canMoveDown, moveUpRef, moveDownRef]);
40
36
  const handleClick = () => {
41
37
  api === null || api === void 0 ? void 0 : api.core.actions.execute(({
42
38
  tr
@@ -15,8 +15,8 @@ const MoveUpDropdownItemContent = ({
15
15
  formatMessage
16
16
  } = useIntl();
17
17
  const {
18
- moveFocusTo,
19
- moveUpRef
18
+ moveUpRef,
19
+ moveDownRef
20
20
  } = useBlockMenu();
21
21
  const {
22
22
  canMoveUp
@@ -29,14 +29,10 @@ const MoveUpDropdownItemContent = ({
29
29
  };
30
30
  });
31
31
  useEffect(() => {
32
- const moveUpElement = moveUpRef.current;
33
- if (!moveUpElement) {
34
- return;
32
+ if (!canMoveUp && moveUpRef.current && moveUpRef.current === document.activeElement && moveDownRef.current) {
33
+ moveDownRef.current.focus();
35
34
  }
36
- if (!canMoveUp && moveUpElement === document.activeElement) {
37
- moveFocusTo('moveDown');
38
- }
39
- }, [canMoveUp, moveFocusTo, moveUpRef]);
35
+ }, [canMoveUp, moveDownRef, moveUpRef]);
40
36
  const handleClick = () => {
41
37
  api === null || api === void 0 ? void 0 : api.core.actions.execute(({
42
38
  tr
@@ -0,0 +1,86 @@
1
+ import { NODE_CATEGORY_BY_TYPE } from '../types';
2
+
3
+ /**
4
+ * Determines if a node is a text node (heading or paragraph).
5
+ * Only text nodes should have their inline content extracted for decisionItem.
6
+ * All other nodes should break out.
7
+ */
8
+ var isTextNode = function isTextNode(node) {
9
+ var category = NODE_CATEGORY_BY_TYPE[node.type.name];
10
+ return category === 'text';
11
+ };
12
+
13
+ /**
14
+ * Creates a decisionItem with the given inline content.
15
+ */
16
+ var createDecisionItem = function createDecisionItem(inlineContent, schema) {
17
+ var decisionItemType = schema.nodes.decisionItem;
18
+ if (!decisionItemType) {
19
+ return null;
20
+ }
21
+ var canContentBeWrappedInDecisionItem = decisionItemType.validContent(inlineContent);
22
+
23
+ // Check if the content is valid for decisionItem
24
+ if (!canContentBeWrappedInDecisionItem) {
25
+ return null;
26
+ }
27
+ return decisionItemType.createAndFill({}, inlineContent);
28
+ };
29
+
30
+ /**
31
+ * Creates a decisionList containing the given decisionItems.
32
+ */
33
+ var createDecisionListWithItems = function createDecisionListWithItems(decisionItems, schema) {
34
+ var decisionListType = schema.nodes.decisionList;
35
+ if (!decisionListType || decisionItems.length === 0) {
36
+ return null;
37
+ }
38
+ return decisionListType.createAndFill({}, decisionItems);
39
+ };
40
+
41
+ /**
42
+ * Wraps blockquote content into decisionList:
43
+ * - Text nodes (paragraph, heading) have their inline content extracted and wrapped in decisionItems
44
+ * - Consecutive text nodes are grouped into a single decisionList with multiple decisionItems
45
+ * - All other nodes break out (lists, code blocks, media, tables, macros, containers, etc.)
46
+ *
47
+ * The logic follows the transform rules:
48
+ * - Only flatten text nodes into decisionItem (since that's the intent - converting text to decisions)
49
+ * - Structures that can't be represented in a decisionItem should break out unchanged
50
+ * - When a break-out node is encountered, flush accumulated decisionItems into a decisionList
51
+ *
52
+ * Example: blockquote(p('a'), p('b'), ul(...), p('c')) → [decisionList(decisionItem('a'), decisionItem('b')), ul(...), decisionList(decisionItem('c'))]
53
+ */
54
+ export var wrapBlockquoteToDecisionListStep = function wrapBlockquoteToDecisionListStep(nodes, context) {
55
+ var schema = context.schema;
56
+ var decisionItemType = schema.nodes.decisionItem;
57
+ if (!decisionItemType) {
58
+ return nodes;
59
+ }
60
+ var result = [];
61
+ var currentDecisionItems = [];
62
+ var flushCurrentDecisionList = function flushCurrentDecisionList() {
63
+ if (currentDecisionItems.length > 0) {
64
+ var decisionList = createDecisionListWithItems(currentDecisionItems, schema);
65
+ if (decisionList) {
66
+ result.push(decisionList);
67
+ }
68
+ currentDecisionItems = [];
69
+ }
70
+ };
71
+ nodes.forEach(function (node) {
72
+ var decisionItem = isTextNode(node) ? createDecisionItem(node.content, schema) : null;
73
+ if (decisionItem) {
74
+ // Accumulate consecutive decisionItems
75
+ currentDecisionItems.push(decisionItem);
76
+ } else {
77
+ // Content can't be wrapped in decisionItem - break out the node
78
+ flushCurrentDecisionList();
79
+ result.push(node);
80
+ }
81
+ });
82
+
83
+ // Flush any remaining decisionItems
84
+ flushCurrentDecisionList();
85
+ return result.length > 0 ? result : nodes;
86
+ };
@@ -40,11 +40,7 @@ var canWrapInTarget = function canWrapInTarget(node, targetNodeType, targetNodeT
40
40
  }
41
41
 
42
42
  // Use the schema to determine if this node can be contained in the target
43
- try {
44
- return targetNodeType.validContent(Fragment.from(node));
45
- } catch (_unused) {
46
- return false;
47
- }
43
+ return targetNodeType.validContent(Fragment.from(node));
48
44
  };
49
45
 
50
46
  /**
@@ -6,6 +6,7 @@ import { listToDecisionListStep } from './steps/listToDecisionListStep';
6
6
  import { listToListStep } from './steps/listToListStep';
7
7
  import { unwrapLayoutStep } from './steps/unwrapLayoutStep';
8
8
  import { unwrapListStep } from './steps/unwrapListStep';
9
+ import { wrapBlockquoteToDecisionListStep } from './steps/wrapBlockquoteToDecisionListStep';
9
10
  import { wrapMixedContentStep } from './steps/wrapMixedContentStep';
10
11
  import { stubStep } from './stubStep';
11
12
  import { NODE_CATEGORY_BY_TYPE, toNodeTypeValue } from './types';
@@ -78,7 +79,8 @@ var TRANSFORM_STEPS_OVERRIDE = {
78
79
  expand: [wrapStep],
79
80
  nestedExpand: [wrapStep],
80
81
  layoutSection: [wrapIntoLayoutStep],
81
- codeBlock: [unwrapStep, flattenStep, wrapStep]
82
+ codeBlock: [unwrapStep, flattenStep, wrapStep],
83
+ decisionList: [unwrapStep, wrapBlockquoteToDecisionListStep]
82
84
  },
83
85
  layoutSection: {
84
86
  blockquote: [unwrapLayoutStep, wrapStep],
@@ -89,4 +89,16 @@ export var isListType = function isListType(node, schema) {
89
89
  return lists.some(function (list) {
90
90
  return list === node.type;
91
91
  });
92
+ };
93
+ export var getBlockNodesInRange = function getBlockNodesInRange(range) {
94
+ if (range.endIndex - range.startIndex <= 1) {
95
+ return [range.parent.child(range.startIndex)];
96
+ }
97
+ var blockNodes = [];
98
+ for (var i = range.startIndex; i < range.endIndex; i++) {
99
+ if (range.parent.child(i).isBlock) {
100
+ blockNodes.push(range.parent.child(i));
101
+ }
102
+ }
103
+ return blockNodes;
92
104
  };
@@ -1,7 +1,6 @@
1
1
  import React, { useCallback, createContext, useContext, useRef } from 'react';
2
2
  var BlockMenuContext = /*#__PURE__*/createContext({
3
3
  onDropdownOpenChanged: function onDropdownOpenChanged() {},
4
- moveFocusTo: function moveFocusTo() {},
5
4
  moveDownRef: /*#__PURE__*/React.createRef(),
6
5
  moveUpRef: /*#__PURE__*/React.createRef()
7
6
  });
@@ -29,19 +28,9 @@ export var BlockMenuProvider = function BlockMenuProvider(_ref) {
29
28
  }, 1);
30
29
  }
31
30
  }, [api]);
32
- var moveFocusTo = useCallback(function (direction) {
33
- if (direction === 'moveUp') {
34
- var _moveUpRef$current;
35
- (_moveUpRef$current = moveUpRef.current) === null || _moveUpRef$current === void 0 || _moveUpRef$current.focus();
36
- } else if (direction === 'moveDown') {
37
- var _moveDownRef$current;
38
- (_moveDownRef$current = moveDownRef.current) === null || _moveDownRef$current === void 0 || _moveDownRef$current.focus();
39
- }
40
- }, []);
41
31
  return /*#__PURE__*/React.createElement(BlockMenuContext.Provider, {
42
32
  value: {
43
33
  onDropdownOpenChanged: onDropdownOpenChanged,
44
- moveFocusTo: moveFocusTo,
45
34
  moveDownRef: moveDownRef,
46
35
  moveUpRef: moveUpRef
47
36
  }
@@ -13,7 +13,7 @@ var MoveDownDropdownItemContent = function MoveDownDropdownItemContent(_ref) {
13
13
  var _useIntl = useIntl(),
14
14
  formatMessage = _useIntl.formatMessage;
15
15
  var _useBlockMenu = useBlockMenu(),
16
- moveFocusTo = _useBlockMenu.moveFocusTo,
16
+ moveUpRef = _useBlockMenu.moveUpRef,
17
17
  moveDownRef = _useBlockMenu.moveDownRef;
18
18
  var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['blockControls'], function (_ref2) {
19
19
  var _blockControlsState$b;
@@ -24,14 +24,10 @@ var MoveDownDropdownItemContent = function MoveDownDropdownItemContent(_ref) {
24
24
  }),
25
25
  canMoveDown = _useSharedPluginState.canMoveDown;
26
26
  useEffect(function () {
27
- var moveDownElement = moveDownRef.current;
28
- if (!moveDownElement) {
29
- return;
27
+ if (!canMoveDown && moveDownRef.current && moveDownRef.current === document.activeElement && moveUpRef.current) {
28
+ moveUpRef.current.focus();
30
29
  }
31
- if (!canMoveDown && moveDownElement === document.activeElement) {
32
- moveFocusTo('moveUp');
33
- }
34
- }, [canMoveDown, moveFocusTo, moveDownRef]);
30
+ }, [canMoveDown, moveUpRef, moveDownRef]);
35
31
  var handleClick = function handleClick() {
36
32
  api === null || api === void 0 || api.core.actions.execute(function (_ref3) {
37
33
  var _api$analytics, _api$blockControls;
@@ -13,8 +13,8 @@ var MoveUpDropdownItemContent = function MoveUpDropdownItemContent(_ref) {
13
13
  var _useIntl = useIntl(),
14
14
  formatMessage = _useIntl.formatMessage;
15
15
  var _useBlockMenu = useBlockMenu(),
16
- moveFocusTo = _useBlockMenu.moveFocusTo,
17
- moveUpRef = _useBlockMenu.moveUpRef;
16
+ moveUpRef = _useBlockMenu.moveUpRef,
17
+ moveDownRef = _useBlockMenu.moveDownRef;
18
18
  var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['blockControls'], function (_ref2) {
19
19
  var _blockControlsState$b;
20
20
  var blockControlsState = _ref2.blockControlsState;
@@ -24,14 +24,10 @@ var MoveUpDropdownItemContent = function MoveUpDropdownItemContent(_ref) {
24
24
  }),
25
25
  canMoveUp = _useSharedPluginState.canMoveUp;
26
26
  useEffect(function () {
27
- var moveUpElement = moveUpRef.current;
28
- if (!moveUpElement) {
29
- return;
27
+ if (!canMoveUp && moveUpRef.current && moveUpRef.current === document.activeElement && moveDownRef.current) {
28
+ moveDownRef.current.focus();
30
29
  }
31
- if (!canMoveUp && moveUpElement === document.activeElement) {
32
- moveFocusTo('moveDown');
33
- }
34
- }, [canMoveUp, moveFocusTo, moveUpRef]);
30
+ }, [canMoveUp, moveDownRef, moveUpRef]);
35
31
  var handleClick = function handleClick() {
36
32
  api === null || api === void 0 || api.core.actions.execute(function (_ref3) {
37
33
  var _api$analytics, _api$blockControls;
@@ -0,0 +1,15 @@
1
+ import type { TransformStep } from '../types';
2
+ /**
3
+ * Wraps blockquote content into decisionList:
4
+ * - Text nodes (paragraph, heading) have their inline content extracted and wrapped in decisionItems
5
+ * - Consecutive text nodes are grouped into a single decisionList with multiple decisionItems
6
+ * - All other nodes break out (lists, code blocks, media, tables, macros, containers, etc.)
7
+ *
8
+ * The logic follows the transform rules:
9
+ * - Only flatten text nodes into decisionItem (since that's the intent - converting text to decisions)
10
+ * - Structures that can't be represented in a decisionItem should break out unchanged
11
+ * - When a break-out node is encountered, flush accumulated decisionItems into a decisionList
12
+ *
13
+ * Example: blockquote(p('a'), p('b'), ul(...), p('c')) → [decisionList(decisionItem('a'), decisionItem('b')), ul(...), decisionList(decisionItem('c'))]
14
+ */
15
+ export declare const wrapBlockquoteToDecisionListStep: TransformStep;
@@ -1,5 +1,4 @@
1
- import type { Schema } from '@atlaskit/editor-prosemirror/model';
2
- import { type Node as PMNode } from '@atlaskit/editor-prosemirror/model';
1
+ import type { Schema, Node as PMNode, NodeRange } from '@atlaskit/editor-prosemirror/model';
3
2
  import type { Selection } from '@atlaskit/editor-prosemirror/state';
4
3
  import { type ContentNodeWithPos } from '@atlaskit/editor-prosemirror/utils';
5
4
  import type { NodeTypeName } from './types';
@@ -17,3 +16,4 @@ export declare const expandSelectionToBlockRange: (selection: Selection, schema:
17
16
  $to: import("prosemirror-model").ResolvedPos;
18
17
  };
19
18
  export declare const isListType: (node: PMNode, schema: Schema) => boolean;
19
+ export declare const getBlockNodesInRange: (range: NodeRange) => PMNode[];
@@ -8,11 +8,6 @@ type BlockMenuProviderProps = {
8
8
  };
9
9
  export type BlockMenuContextType = {
10
10
  moveDownRef: React.MutableRefObject<HTMLButtonElement | null>;
11
- /**
12
- * Function to move focus between move up and move down items.
13
- * Used when one item is disabled and focused.
14
- */
15
- moveFocusTo: (direction: Direction) => void;
16
11
  moveUpRef: React.MutableRefObject<HTMLButtonElement | null>;
17
12
  /**
18
13
  * Callback for when the dropdown is open/closed. Receives an object with `isOpen` state.
@@ -0,0 +1,15 @@
1
+ import type { TransformStep } from '../types';
2
+ /**
3
+ * Wraps blockquote content into decisionList:
4
+ * - Text nodes (paragraph, heading) have their inline content extracted and wrapped in decisionItems
5
+ * - Consecutive text nodes are grouped into a single decisionList with multiple decisionItems
6
+ * - All other nodes break out (lists, code blocks, media, tables, macros, containers, etc.)
7
+ *
8
+ * The logic follows the transform rules:
9
+ * - Only flatten text nodes into decisionItem (since that's the intent - converting text to decisions)
10
+ * - Structures that can't be represented in a decisionItem should break out unchanged
11
+ * - When a break-out node is encountered, flush accumulated decisionItems into a decisionList
12
+ *
13
+ * Example: blockquote(p('a'), p('b'), ul(...), p('c')) → [decisionList(decisionItem('a'), decisionItem('b')), ul(...), decisionList(decisionItem('c'))]
14
+ */
15
+ export declare const wrapBlockquoteToDecisionListStep: TransformStep;
@@ -1,5 +1,4 @@
1
- import type { Schema } from '@atlaskit/editor-prosemirror/model';
2
- import { type Node as PMNode } from '@atlaskit/editor-prosemirror/model';
1
+ import type { Schema, Node as PMNode, NodeRange } from '@atlaskit/editor-prosemirror/model';
3
2
  import type { Selection } from '@atlaskit/editor-prosemirror/state';
4
3
  import { type ContentNodeWithPos } from '@atlaskit/editor-prosemirror/utils';
5
4
  import type { NodeTypeName } from './types';
@@ -17,3 +16,4 @@ export declare const expandSelectionToBlockRange: (selection: Selection, schema:
17
16
  $to: import("prosemirror-model").ResolvedPos;
18
17
  };
19
18
  export declare const isListType: (node: PMNode, schema: Schema) => boolean;
19
+ export declare const getBlockNodesInRange: (range: NodeRange) => PMNode[];
@@ -8,11 +8,6 @@ type BlockMenuProviderProps = {
8
8
  };
9
9
  export type BlockMenuContextType = {
10
10
  moveDownRef: React.MutableRefObject<HTMLButtonElement | null>;
11
- /**
12
- * Function to move focus between move up and move down items.
13
- * Used when one item is disabled and focused.
14
- */
15
- moveFocusTo: (direction: Direction) => void;
16
11
  moveUpRef: React.MutableRefObject<HTMLButtonElement | null>;
17
12
  /**
18
13
  * Callback for when the dropdown is open/closed. Receives an object with `isOpen` state.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-block-menu",
3
- "version": "5.2.5",
3
+ "version": "5.2.7",
4
4
  "description": "BlockMenu plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -35,7 +35,7 @@
35
35
  "@atlaskit/editor-plugin-decorations": "^6.1.0",
36
36
  "@atlaskit/editor-plugin-selection": "^6.1.0",
37
37
  "@atlaskit/editor-plugin-user-intent": "^4.0.0",
38
- "@atlaskit/editor-prosemirror": "7.0.0",
38
+ "@atlaskit/editor-prosemirror": "^7.2.0",
39
39
  "@atlaskit/editor-shared-styles": "^3.10.0",
40
40
  "@atlaskit/editor-tables": "^2.9.0",
41
41
  "@atlaskit/editor-toolbar": "^0.18.0",
@@ -44,7 +44,7 @@
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": "^15.7.0",
47
+ "@atlaskit/tmp-editor-statsig": "^15.9.0",
48
48
  "@atlaskit/tokens": "^8.4.0",
49
49
  "@babel/runtime": "^7.0.0"
50
50
  },