@atlaskit/editor-plugin-block-menu 5.2.11 → 5.2.12

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 (42) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/cjs/ui/block-menu-renderer/BlockMenuComponent.js +37 -0
  3. package/dist/cjs/ui/block-menu-renderer/BlockMenuComponents.js +29 -0
  4. package/dist/cjs/ui/block-menu-renderer/BlockMenuRenderer.js +33 -0
  5. package/dist/cjs/ui/block-menu-renderer/fallbacks.js +32 -0
  6. package/dist/cjs/ui/block-menu-renderer/types.js +5 -0
  7. package/dist/cjs/ui/block-menu-renderer/utils.js +127 -0
  8. package/dist/cjs/ui/block-menu.js +7 -20
  9. package/dist/es2019/ui/block-menu-renderer/BlockMenuComponent.js +31 -0
  10. package/dist/es2019/ui/block-menu-renderer/BlockMenuComponents.js +21 -0
  11. package/dist/es2019/ui/block-menu-renderer/BlockMenuRenderer.js +24 -0
  12. package/dist/es2019/ui/block-menu-renderer/fallbacks.js +21 -0
  13. package/dist/es2019/ui/block-menu-renderer/types.js +1 -0
  14. package/dist/es2019/ui/block-menu-renderer/utils.js +93 -0
  15. package/dist/es2019/ui/block-menu.js +6 -13
  16. package/dist/esm/ui/block-menu-renderer/BlockMenuComponent.js +30 -0
  17. package/dist/esm/ui/block-menu-renderer/BlockMenuComponents.js +22 -0
  18. package/dist/esm/ui/block-menu-renderer/BlockMenuRenderer.js +25 -0
  19. package/dist/esm/ui/block-menu-renderer/fallbacks.js +25 -0
  20. package/dist/esm/ui/block-menu-renderer/types.js +1 -0
  21. package/dist/esm/ui/block-menu-renderer/utils.js +121 -0
  22. package/dist/esm/ui/block-menu.js +6 -19
  23. package/dist/types/blockMenuPluginType.d.ts +3 -2
  24. package/dist/types/ui/block-menu-renderer/BlockMenuComponent.d.ts +11 -0
  25. package/dist/types/ui/block-menu-renderer/BlockMenuComponents.d.ts +12 -0
  26. package/dist/types/ui/block-menu-renderer/BlockMenuRenderer.d.ts +12 -0
  27. package/dist/types/ui/block-menu-renderer/fallbacks.d.ts +2 -0
  28. package/dist/types/ui/block-menu-renderer/types.d.ts +27 -0
  29. package/dist/types/ui/block-menu-renderer/utils.d.ts +37 -0
  30. package/dist/types-ts4.5/blockMenuPluginType.d.ts +3 -2
  31. package/dist/types-ts4.5/ui/block-menu-renderer/BlockMenuComponent.d.ts +11 -0
  32. package/dist/types-ts4.5/ui/block-menu-renderer/BlockMenuComponents.d.ts +12 -0
  33. package/dist/types-ts4.5/ui/block-menu-renderer/BlockMenuRenderer.d.ts +12 -0
  34. package/dist/types-ts4.5/ui/block-menu-renderer/fallbacks.d.ts +2 -0
  35. package/dist/types-ts4.5/ui/block-menu-renderer/types.d.ts +27 -0
  36. package/dist/types-ts4.5/ui/block-menu-renderer/utils.d.ts +37 -0
  37. package/package.json +1 -1
  38. package/dist/cjs/ui/block-menu-renderer.js +0 -104
  39. package/dist/es2019/ui/block-menu-renderer.js +0 -83
  40. package/dist/esm/ui/block-menu-renderer.js +0 -95
  41. package/dist/types/ui/block-menu-renderer.d.ts +0 -18
  42. package/dist/types-ts4.5/ui/block-menu-renderer.d.ts +0 -18
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @atlaskit/editor-plugin-block-menu
2
2
 
3
+ ## 5.2.12
4
+
5
+ ### Patch Changes
6
+
7
+ - [`b3c4249a34e4b`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/b3c4249a34e4b) -
8
+ EDITOR-3880 Improve block menu rendering to support infinite nesting
9
+
3
10
  ## 5.2.11
4
11
 
5
12
  ### Patch Changes
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.BlockMenuComponent = void 0;
8
+ var _react = _interopRequireDefault(require("react"));
9
+ var _BlockMenuComponents = require("./BlockMenuComponents");
10
+ var _utils = require("./utils");
11
+ /**
12
+ * Renders the given registered component based on its type
13
+ */
14
+ var BlockMenuComponent = exports.BlockMenuComponent = function BlockMenuComponent(_ref) {
15
+ var registeredComponent = _ref.registeredComponent,
16
+ childrenMap = _ref.childrenMap,
17
+ fallbacks = _ref.fallbacks;
18
+ if (registeredComponent.type === 'block-menu-item') {
19
+ var ItemComponent = registeredComponent.component || fallbacks['block-menu-item'];
20
+ return /*#__PURE__*/_react.default.createElement(ItemComponent, {
21
+ key: registeredComponent.key
22
+ });
23
+ }
24
+ if (!(0, _utils.willComponentRender)(registeredComponent, childrenMap)) {
25
+ return null;
26
+ }
27
+ var ParentComponent = registeredComponent.component || fallbacks[registeredComponent.type];
28
+ var childrenMapKey = (0, _utils.getChildrenMapKey)(registeredComponent.key, registeredComponent.type);
29
+ var registeredComponents = childrenMap.get(childrenMapKey);
30
+ return /*#__PURE__*/_react.default.createElement(ParentComponent, {
31
+ key: registeredComponent.key
32
+ }, /*#__PURE__*/_react.default.createElement(_BlockMenuComponents.BlockMenuComponents, {
33
+ registeredComponents: registeredComponents,
34
+ childrenMap: childrenMap,
35
+ fallbacks: fallbacks
36
+ }));
37
+ };
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.BlockMenuComponents = void 0;
8
+ var _react = _interopRequireDefault(require("react"));
9
+ var _BlockMenuComponent = require("./BlockMenuComponent");
10
+ /**
11
+ * Renders the given registered components
12
+ * Returns null if no components are rendered
13
+ */
14
+ var BlockMenuComponents = exports.BlockMenuComponents = function BlockMenuComponents(_ref) {
15
+ var registeredComponents = _ref.registeredComponents,
16
+ childrenMap = _ref.childrenMap,
17
+ fallbacks = _ref.fallbacks;
18
+ if (!(registeredComponents !== null && registeredComponents !== void 0 && registeredComponents.length)) {
19
+ return null;
20
+ }
21
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, registeredComponents.map(function (registeredComponent) {
22
+ return /*#__PURE__*/_react.default.createElement(_BlockMenuComponent.BlockMenuComponent, {
23
+ key: registeredComponent.key,
24
+ registeredComponent: registeredComponent,
25
+ childrenMap: childrenMap,
26
+ fallbacks: fallbacks
27
+ });
28
+ }));
29
+ };
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+
3
+ var _typeof = require("@babel/runtime/helpers/typeof");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.BlockMenuRenderer = void 0;
8
+ var _react = _interopRequireWildcard(require("react"));
9
+ var _BlockMenuComponents = require("./BlockMenuComponents");
10
+ var _fallbacks = require("./fallbacks");
11
+ var _utils = require("./utils");
12
+ 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
+ * BlockMenuRenderer orchestrates the rendering of the entire block menu hierarchy
15
+ */
16
+ var BlockMenuRenderer = exports.BlockMenuRenderer = function BlockMenuRenderer(_ref) {
17
+ var allRegisteredComponents = _ref.allRegisteredComponents,
18
+ _ref$fallbacks = _ref.fallbacks,
19
+ fallbacks = _ref$fallbacks === void 0 ? _fallbacks.BLOCK_MENU_FALLBACKS : _ref$fallbacks;
20
+ var _useMemo = (0, _react.useMemo)(function () {
21
+ return {
22
+ childrenMap: (0, _utils.buildChildrenMap)(allRegisteredComponents),
23
+ topLevelSections: (0, _utils.getSortedTopLevelSections)(allRegisteredComponents)
24
+ };
25
+ }, [allRegisteredComponents]),
26
+ childrenMap = _useMemo.childrenMap,
27
+ topLevelSections = _useMemo.topLevelSections;
28
+ return /*#__PURE__*/_react.default.createElement(_BlockMenuComponents.BlockMenuComponents, {
29
+ registeredComponents: topLevelSections,
30
+ childrenMap: childrenMap,
31
+ fallbacks: fallbacks
32
+ });
33
+ };
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.BLOCK_MENU_FALLBACKS = void 0;
8
+ var _react = _interopRequireDefault(require("react"));
9
+ var _editorToolbar = require("@atlaskit/editor-toolbar");
10
+ var _chevronRight = _interopRequireDefault(require("@atlaskit/icon/core/chevron-right"));
11
+ var BLOCK_MENU_FALLBACKS = exports.BLOCK_MENU_FALLBACKS = {
12
+ 'block-menu-nested': function blockMenuNested(_ref) {
13
+ var children = _ref.children;
14
+ return /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarNestedDropdownMenu, {
15
+ elemBefore: undefined,
16
+ elemAfter: /*#__PURE__*/_react.default.createElement(_chevronRight.default, {
17
+ label: ""
18
+ }),
19
+ text: "Nested Menu",
20
+ enableMaxHeight: true,
21
+ shouldFitContainer: true
22
+ }, children);
23
+ },
24
+ 'block-menu-section': function blockMenuSection(_ref2) {
25
+ var children = _ref2.children;
26
+ return /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarDropdownItemSection, null, children);
27
+ },
28
+ // eslint-disable-next-line @atlassian/i18n/no-literal-string-in-jsx
29
+ 'block-menu-item': function blockMenuItem() {
30
+ return /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarDropdownItem, null, "Block Menu Item");
31
+ }
32
+ };
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.willComponentRender = exports.getSortedTopLevelSections = exports.getChildrenMapKey = exports.buildChildrenMap = void 0;
8
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
10
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
11
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
12
+ /**
13
+ * Type guard to check if a component has a parent
14
+ *
15
+ * @param component The block menu component to check
16
+ * @returns True if the component has a parent, false otherwise
17
+ */
18
+ var hasParent = function hasParent(component) {
19
+ return 'parent' in component && !!component.parent;
20
+ };
21
+
22
+ /**
23
+ * Type guard to identify top-level sections (sections without a parent)
24
+ *
25
+ * @param component The block menu component to check
26
+ * @returns True if the component is a top-level section, false otherwise
27
+ */
28
+ var isTopLevelSection = function isTopLevelSection(component) {
29
+ return component.type === 'block-menu-section' && !hasParent(component);
30
+ };
31
+
32
+ /**
33
+ * Gets all top-level sections (those without a parent) sorted by rank
34
+ *
35
+ * @param components All registered block menu components
36
+ * @returns Sorted array of top-level sections
37
+ */
38
+ var getSortedTopLevelSections = exports.getSortedTopLevelSections = function getSortedTopLevelSections(components) {
39
+ return components.filter(isTopLevelSection).sort(function (a, b) {
40
+ return (a.rank || 0) - (b.rank || 0);
41
+ });
42
+ };
43
+
44
+ /**
45
+ * Generates a unique key from a key and type
46
+ * Used to lookup children in the childrenMap
47
+ *
48
+ * @param key The component's key
49
+ * @param type The component's type
50
+ * @returns A unique string key combining type and key
51
+ */
52
+ var getChildrenMapKey = exports.getChildrenMapKey = function getChildrenMapKey(key, type) {
53
+ return "".concat(type, ":").concat(key);
54
+ };
55
+
56
+ /**
57
+ * Builds a map of parent keys to their sorted children
58
+ * This enables efficient hierarchical rendering of the menu structure
59
+ *
60
+ * @param components All registered block menu components
61
+ * @returns Map where keys are parent identifiers and values are sorted child components
62
+ */
63
+ var buildChildrenMap = exports.buildChildrenMap = function buildChildrenMap(components) {
64
+ var childrenMap = new Map();
65
+ var _iterator = _createForOfIteratorHelper(components),
66
+ _step;
67
+ try {
68
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
69
+ var component = _step.value;
70
+ // Only components with parents can be children
71
+ if ('parent' in component && !!component.parent) {
72
+ var childrenMapKey = getChildrenMapKey(component.parent.key, component.parent.type);
73
+ var existing = childrenMap.get(childrenMapKey) || [];
74
+ existing.push(component);
75
+ childrenMap.set(childrenMapKey, existing);
76
+ }
77
+ }
78
+
79
+ // Sort children by their rank within their parent
80
+ } catch (err) {
81
+ _iterator.e(err);
82
+ } finally {
83
+ _iterator.f();
84
+ }
85
+ var _iterator2 = _createForOfIteratorHelper(childrenMap.entries()),
86
+ _step2;
87
+ try {
88
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
89
+ var _step2$value = (0, _slicedToArray2.default)(_step2.value, 2),
90
+ children = _step2$value[1];
91
+ children.sort(function (a, b) {
92
+ var rankA = hasParent(a) ? a.parent.rank || 0 : 0;
93
+ var rankB = hasParent(b) ? b.parent.rank || 0 : 0;
94
+ return rankA - rankB;
95
+ });
96
+ }
97
+ } catch (err) {
98
+ _iterator2.e(err);
99
+ } finally {
100
+ _iterator2.f();
101
+ }
102
+ return childrenMap;
103
+ };
104
+
105
+ /**
106
+ * Determines whether a component will render based on its type and children
107
+ *
108
+ * Rules:
109
+ * - An item will not render if has a component that returns null
110
+ * - A nested menu will render if it has at least one registered child component
111
+ * - A section will render if it has at least one registered child component that will render
112
+ *
113
+ * NOTE: This requires invoking each item's component function to check for null return
114
+ */
115
+ var _willComponentRender = exports.willComponentRender = function willComponentRender(registeredComponent, childrenMap) {
116
+ if (registeredComponent.type === 'block-menu-item') {
117
+ return registeredComponent.component ? registeredComponent.component() !== null : true;
118
+ }
119
+ var childrenMapKey = getChildrenMapKey(registeredComponent.key, registeredComponent.type);
120
+ var registeredComponents = childrenMap.get(childrenMapKey) || [];
121
+ if (registeredComponent.type === 'block-menu-nested') {
122
+ return registeredComponents.length > 0;
123
+ }
124
+ return registeredComponents.some(function (childComponent) {
125
+ return _willComponentRender(childComponent, childrenMap);
126
+ });
127
+ };
@@ -19,15 +19,15 @@ var _hooks = require("@atlaskit/editor-common/hooks");
19
19
  var _selection = require("@atlaskit/editor-common/selection");
20
20
  var _styles = require("@atlaskit/editor-common/styles");
21
21
  var _ui = require("@atlaskit/editor-common/ui");
22
+ var _uiMenu = require("@atlaskit/editor-common/ui-menu");
22
23
  var _uiReact = require("@atlaskit/editor-common/ui-react");
23
24
  var _editorSharedStyles = require("@atlaskit/editor-shared-styles");
24
- var _editorToolbar = require("@atlaskit/editor-toolbar");
25
25
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
26
26
  var _platformFeatureFlagsReact = require("@atlaskit/platform-feature-flags-react");
27
27
  var _compiled = require("@atlaskit/primitives/compiled");
28
28
  var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
29
29
  var _blockMenuProvider = require("./block-menu-provider");
30
- var _blockMenuRenderer = require("./block-menu-renderer");
30
+ var _BlockMenuRenderer = require("./block-menu-renderer/BlockMenuRenderer");
31
31
  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); }
32
32
  var styles = {
33
33
  base: "_2rko12b0 _bfhk1bhr _16qs130s",
@@ -120,24 +120,11 @@ var BlockMenuContent = function BlockMenuContent(_ref3) {
120
120
  testId: "editor-block-menu",
121
121
  ref: ref,
122
122
  xcss: (0, _css.cx)(styles.base, (0, _experiments.editorExperiment)('platform_synced_block', true) && styles.emptyMenuSectionStyles)
123
- }, /*#__PURE__*/_react.default.createElement(_blockMenuRenderer.BlockMenuRenderer, {
124
- components: blockMenuComponents || [],
125
- fallbacks: {
126
- nestedMenu: function nestedMenu() {
127
- return /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarNestedDropdownMenu, {
128
- elemBefore: undefined,
129
- elemAfter: undefined
130
- }, /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarDropdownItemSection, null, /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarDropdownItem, null, "Block Menu Item")));
131
- },
132
- section: function section() {
133
- return /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarDropdownItemSection, null, /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarDropdownItem, null, "Block Menu Item"));
134
- },
135
- // eslint-disable-next-line @atlassian/i18n/no-literal-string-in-jsx
136
- item: function item() {
137
- return /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarDropdownItem, null, "Block Menu Item");
138
- }
139
- }
140
- }));
123
+ }, /*#__PURE__*/_react.default.createElement(_uiMenu.ArrowKeyNavigationProvider, {
124
+ type: _uiMenu.ArrowKeyNavigationType.MENU
125
+ }, /*#__PURE__*/_react.default.createElement(_BlockMenuRenderer.BlockMenuRenderer, {
126
+ allRegisteredComponents: blockMenuComponents || []
127
+ })));
141
128
  };
142
129
  var BlockMenu = function BlockMenu(_ref4) {
143
130
  var _editorView$dom, _ref5;
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import { BlockMenuComponents } from './BlockMenuComponents';
3
+ import { getChildrenMapKey, willComponentRender } from './utils';
4
+ /**
5
+ * Renders the given registered component based on its type
6
+ */
7
+ export const BlockMenuComponent = ({
8
+ registeredComponent,
9
+ childrenMap,
10
+ fallbacks
11
+ }) => {
12
+ if (registeredComponent.type === 'block-menu-item') {
13
+ const ItemComponent = registeredComponent.component || fallbacks['block-menu-item'];
14
+ return /*#__PURE__*/React.createElement(ItemComponent, {
15
+ key: registeredComponent.key
16
+ });
17
+ }
18
+ if (!willComponentRender(registeredComponent, childrenMap)) {
19
+ return null;
20
+ }
21
+ const ParentComponent = registeredComponent.component || fallbacks[registeredComponent.type];
22
+ const childrenMapKey = getChildrenMapKey(registeredComponent.key, registeredComponent.type);
23
+ const registeredComponents = childrenMap.get(childrenMapKey);
24
+ return /*#__PURE__*/React.createElement(ParentComponent, {
25
+ key: registeredComponent.key
26
+ }, /*#__PURE__*/React.createElement(BlockMenuComponents, {
27
+ registeredComponents: registeredComponents,
28
+ childrenMap: childrenMap,
29
+ fallbacks: fallbacks
30
+ }));
31
+ };
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { BlockMenuComponent } from './BlockMenuComponent';
3
+ /**
4
+ * Renders the given registered components
5
+ * Returns null if no components are rendered
6
+ */
7
+ export const BlockMenuComponents = ({
8
+ registeredComponents,
9
+ childrenMap,
10
+ fallbacks
11
+ }) => {
12
+ if (!(registeredComponents !== null && registeredComponents !== void 0 && registeredComponents.length)) {
13
+ return null;
14
+ }
15
+ return /*#__PURE__*/React.createElement(React.Fragment, null, registeredComponents.map(registeredComponent => /*#__PURE__*/React.createElement(BlockMenuComponent, {
16
+ key: registeredComponent.key,
17
+ registeredComponent: registeredComponent,
18
+ childrenMap: childrenMap,
19
+ fallbacks: fallbacks
20
+ })));
21
+ };
@@ -0,0 +1,24 @@
1
+ import React, { useMemo } from 'react';
2
+ import { BlockMenuComponents } from './BlockMenuComponents';
3
+ import { BLOCK_MENU_FALLBACKS } from './fallbacks';
4
+ import { buildChildrenMap, getSortedTopLevelSections } from './utils';
5
+ /**
6
+ * BlockMenuRenderer orchestrates the rendering of the entire block menu hierarchy
7
+ */
8
+ export const BlockMenuRenderer = ({
9
+ allRegisteredComponents,
10
+ fallbacks = BLOCK_MENU_FALLBACKS
11
+ }) => {
12
+ const {
13
+ childrenMap,
14
+ topLevelSections
15
+ } = useMemo(() => ({
16
+ childrenMap: buildChildrenMap(allRegisteredComponents),
17
+ topLevelSections: getSortedTopLevelSections(allRegisteredComponents)
18
+ }), [allRegisteredComponents]);
19
+ return /*#__PURE__*/React.createElement(BlockMenuComponents, {
20
+ registeredComponents: topLevelSections,
21
+ childrenMap: childrenMap,
22
+ fallbacks: fallbacks
23
+ });
24
+ };
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { ToolbarDropdownItem, ToolbarDropdownItemSection, ToolbarNestedDropdownMenu } from '@atlaskit/editor-toolbar';
3
+ import ChevronRightIcon from '@atlaskit/icon/core/chevron-right';
4
+ export const BLOCK_MENU_FALLBACKS = {
5
+ 'block-menu-nested': ({
6
+ children
7
+ }) => /*#__PURE__*/React.createElement(ToolbarNestedDropdownMenu, {
8
+ elemBefore: undefined,
9
+ elemAfter: /*#__PURE__*/React.createElement(ChevronRightIcon, {
10
+ label: ""
11
+ }),
12
+ text: "Nested Menu",
13
+ enableMaxHeight: true,
14
+ shouldFitContainer: true
15
+ }, children),
16
+ 'block-menu-section': ({
17
+ children
18
+ }) => /*#__PURE__*/React.createElement(ToolbarDropdownItemSection, null, children),
19
+ // eslint-disable-next-line @atlassian/i18n/no-literal-string-in-jsx
20
+ 'block-menu-item': () => /*#__PURE__*/React.createElement(ToolbarDropdownItem, null, "Block Menu Item")
21
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Type guard to check if a component has a parent
3
+ *
4
+ * @param component The block menu component to check
5
+ * @returns True if the component has a parent, false otherwise
6
+ */
7
+ const hasParent = component => {
8
+ return 'parent' in component && !!component.parent;
9
+ };
10
+
11
+ /**
12
+ * Type guard to identify top-level sections (sections without a parent)
13
+ *
14
+ * @param component The block menu component to check
15
+ * @returns True if the component is a top-level section, false otherwise
16
+ */
17
+ const isTopLevelSection = component => {
18
+ return component.type === 'block-menu-section' && !hasParent(component);
19
+ };
20
+
21
+ /**
22
+ * Gets all top-level sections (those without a parent) sorted by rank
23
+ *
24
+ * @param components All registered block menu components
25
+ * @returns Sorted array of top-level sections
26
+ */
27
+ export const getSortedTopLevelSections = components => {
28
+ return components.filter(isTopLevelSection).sort((a, b) => (a.rank || 0) - (b.rank || 0));
29
+ };
30
+
31
+ /**
32
+ * Generates a unique key from a key and type
33
+ * Used to lookup children in the childrenMap
34
+ *
35
+ * @param key The component's key
36
+ * @param type The component's type
37
+ * @returns A unique string key combining type and key
38
+ */
39
+ export const getChildrenMapKey = (key, type) => {
40
+ return `${type}:${key}`;
41
+ };
42
+
43
+ /**
44
+ * Builds a map of parent keys to their sorted children
45
+ * This enables efficient hierarchical rendering of the menu structure
46
+ *
47
+ * @param components All registered block menu components
48
+ * @returns Map where keys are parent identifiers and values are sorted child components
49
+ */
50
+ export const buildChildrenMap = components => {
51
+ const childrenMap = new Map();
52
+ for (const component of components) {
53
+ // Only components with parents can be children
54
+ if ('parent' in component && !!component.parent) {
55
+ const childrenMapKey = getChildrenMapKey(component.parent.key, component.parent.type);
56
+ const existing = childrenMap.get(childrenMapKey) || [];
57
+ existing.push(component);
58
+ childrenMap.set(childrenMapKey, existing);
59
+ }
60
+ }
61
+
62
+ // Sort children by their rank within their parent
63
+ for (const [, children] of childrenMap.entries()) {
64
+ children.sort((a, b) => {
65
+ const rankA = hasParent(a) ? a.parent.rank || 0 : 0;
66
+ const rankB = hasParent(b) ? b.parent.rank || 0 : 0;
67
+ return rankA - rankB;
68
+ });
69
+ }
70
+ return childrenMap;
71
+ };
72
+
73
+ /**
74
+ * Determines whether a component will render based on its type and children
75
+ *
76
+ * Rules:
77
+ * - An item will not render if has a component that returns null
78
+ * - A nested menu will render if it has at least one registered child component
79
+ * - A section will render if it has at least one registered child component that will render
80
+ *
81
+ * NOTE: This requires invoking each item's component function to check for null return
82
+ */
83
+ export const willComponentRender = (registeredComponent, childrenMap) => {
84
+ if (registeredComponent.type === 'block-menu-item') {
85
+ return registeredComponent.component ? registeredComponent.component() !== null : true;
86
+ }
87
+ const childrenMapKey = getChildrenMapKey(registeredComponent.key, registeredComponent.type);
88
+ const registeredComponents = childrenMap.get(childrenMapKey) || [];
89
+ if (registeredComponent.type === 'block-menu-nested') {
90
+ return registeredComponents.length > 0;
91
+ }
92
+ return registeredComponents.some(childComponent => willComponentRender(childComponent, childrenMap));
93
+ };
@@ -10,15 +10,15 @@ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks'
10
10
  import { deleteSelectedRange } from '@atlaskit/editor-common/selection';
11
11
  import { DRAG_HANDLE_SELECTOR, DRAG_HANDLE_WIDTH } from '@atlaskit/editor-common/styles';
12
12
  import { Popup } from '@atlaskit/editor-common/ui';
13
+ import { ArrowKeyNavigationProvider, ArrowKeyNavigationType } from '@atlaskit/editor-common/ui-menu';
13
14
  import { OutsideClickTargetRefContext, withReactEditorViewOuterListeners } from '@atlaskit/editor-common/ui-react';
14
15
  import { akEditorFloatingOverlapPanelZIndex } from '@atlaskit/editor-shared-styles';
15
- import { ToolbarDropdownItem, ToolbarDropdownItemSection, ToolbarNestedDropdownMenu } from '@atlaskit/editor-toolbar';
16
16
  import { fg } from '@atlaskit/platform-feature-flags';
17
17
  import { conditionalHooksFactory } from '@atlaskit/platform-feature-flags-react';
18
18
  import { Box } from '@atlaskit/primitives/compiled';
19
19
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
20
20
  import { useBlockMenu } from './block-menu-provider';
21
- import { BlockMenuRenderer } from './block-menu-renderer';
21
+ import { BlockMenuRenderer } from './block-menu-renderer/BlockMenuRenderer';
22
22
  const styles = {
23
23
  base: "_2rko12b0 _bfhk1bhr _16qs130s",
24
24
  emptyMenuSectionStyles: "_1cc0glyw _1k2yglyw"
@@ -111,18 +111,11 @@ const BlockMenuContent = ({
111
111
  testId: "editor-block-menu",
112
112
  ref: ref,
113
113
  xcss: cx(styles.base, editorExperiment('platform_synced_block', true) && styles.emptyMenuSectionStyles)
114
+ }, /*#__PURE__*/React.createElement(ArrowKeyNavigationProvider, {
115
+ type: ArrowKeyNavigationType.MENU
114
116
  }, /*#__PURE__*/React.createElement(BlockMenuRenderer, {
115
- components: blockMenuComponents || [],
116
- fallbacks: {
117
- nestedMenu: () => /*#__PURE__*/React.createElement(ToolbarNestedDropdownMenu, {
118
- elemBefore: undefined,
119
- elemAfter: undefined
120
- }, /*#__PURE__*/React.createElement(ToolbarDropdownItemSection, null, /*#__PURE__*/React.createElement(ToolbarDropdownItem, null, "Block Menu Item"))),
121
- section: () => /*#__PURE__*/React.createElement(ToolbarDropdownItemSection, null, /*#__PURE__*/React.createElement(ToolbarDropdownItem, null, "Block Menu Item")),
122
- // eslint-disable-next-line @atlassian/i18n/no-literal-string-in-jsx
123
- item: () => /*#__PURE__*/React.createElement(ToolbarDropdownItem, null, "Block Menu Item")
124
- }
125
- }));
117
+ allRegisteredComponents: blockMenuComponents || []
118
+ })));
126
119
  };
127
120
  const BlockMenu = ({
128
121
  editorView,
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ import { BlockMenuComponents } from './BlockMenuComponents';
3
+ import { getChildrenMapKey, willComponentRender } from './utils';
4
+ /**
5
+ * Renders the given registered component based on its type
6
+ */
7
+ export var BlockMenuComponent = function BlockMenuComponent(_ref) {
8
+ var registeredComponent = _ref.registeredComponent,
9
+ childrenMap = _ref.childrenMap,
10
+ fallbacks = _ref.fallbacks;
11
+ if (registeredComponent.type === 'block-menu-item') {
12
+ var ItemComponent = registeredComponent.component || fallbacks['block-menu-item'];
13
+ return /*#__PURE__*/React.createElement(ItemComponent, {
14
+ key: registeredComponent.key
15
+ });
16
+ }
17
+ if (!willComponentRender(registeredComponent, childrenMap)) {
18
+ return null;
19
+ }
20
+ var ParentComponent = registeredComponent.component || fallbacks[registeredComponent.type];
21
+ var childrenMapKey = getChildrenMapKey(registeredComponent.key, registeredComponent.type);
22
+ var registeredComponents = childrenMap.get(childrenMapKey);
23
+ return /*#__PURE__*/React.createElement(ParentComponent, {
24
+ key: registeredComponent.key
25
+ }, /*#__PURE__*/React.createElement(BlockMenuComponents, {
26
+ registeredComponents: registeredComponents,
27
+ childrenMap: childrenMap,
28
+ fallbacks: fallbacks
29
+ }));
30
+ };
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import { BlockMenuComponent } from './BlockMenuComponent';
3
+ /**
4
+ * Renders the given registered components
5
+ * Returns null if no components are rendered
6
+ */
7
+ export var BlockMenuComponents = function BlockMenuComponents(_ref) {
8
+ var registeredComponents = _ref.registeredComponents,
9
+ childrenMap = _ref.childrenMap,
10
+ fallbacks = _ref.fallbacks;
11
+ if (!(registeredComponents !== null && registeredComponents !== void 0 && registeredComponents.length)) {
12
+ return null;
13
+ }
14
+ return /*#__PURE__*/React.createElement(React.Fragment, null, registeredComponents.map(function (registeredComponent) {
15
+ return /*#__PURE__*/React.createElement(BlockMenuComponent, {
16
+ key: registeredComponent.key,
17
+ registeredComponent: registeredComponent,
18
+ childrenMap: childrenMap,
19
+ fallbacks: fallbacks
20
+ });
21
+ }));
22
+ };
@@ -0,0 +1,25 @@
1
+ import React, { useMemo } from 'react';
2
+ import { BlockMenuComponents } from './BlockMenuComponents';
3
+ import { BLOCK_MENU_FALLBACKS } from './fallbacks';
4
+ import { buildChildrenMap, getSortedTopLevelSections } from './utils';
5
+ /**
6
+ * BlockMenuRenderer orchestrates the rendering of the entire block menu hierarchy
7
+ */
8
+ export var BlockMenuRenderer = function BlockMenuRenderer(_ref) {
9
+ var allRegisteredComponents = _ref.allRegisteredComponents,
10
+ _ref$fallbacks = _ref.fallbacks,
11
+ fallbacks = _ref$fallbacks === void 0 ? BLOCK_MENU_FALLBACKS : _ref$fallbacks;
12
+ var _useMemo = useMemo(function () {
13
+ return {
14
+ childrenMap: buildChildrenMap(allRegisteredComponents),
15
+ topLevelSections: getSortedTopLevelSections(allRegisteredComponents)
16
+ };
17
+ }, [allRegisteredComponents]),
18
+ childrenMap = _useMemo.childrenMap,
19
+ topLevelSections = _useMemo.topLevelSections;
20
+ return /*#__PURE__*/React.createElement(BlockMenuComponents, {
21
+ registeredComponents: topLevelSections,
22
+ childrenMap: childrenMap,
23
+ fallbacks: fallbacks
24
+ });
25
+ };