@atlaskit/editor-plugin-layout 10.7.1 → 10.8.1

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 (38) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cjs/pm-plugins/actions.js +33 -28
  3. package/dist/cjs/pm-plugins/utils/{redistribute-proportionally.js → layout-column-distribution.js} +39 -3
  4. package/dist/cjs/pm-plugins/utils/layout-column-selection.js +4 -3
  5. package/dist/cjs/ui/LayoutColumnMenu/DistributeColumnsDropdownItem.js +4 -14
  6. package/dist/cjs/ui/LayoutColumnMenu/VerticalAlignDropdownItem.js +2 -1
  7. package/dist/cjs/ui/LayoutColumnMenu/VerticalAlignNestedMenu.js +1 -4
  8. package/dist/cjs/ui/LayoutColumnMenu/components.js +18 -8
  9. package/dist/cjs/ui/LayoutColumnMenu/index.js +3 -36
  10. package/dist/es2019/pm-plugins/actions.js +27 -15
  11. package/dist/es2019/pm-plugins/utils/{redistribute-proportionally.js → layout-column-distribution.js} +34 -1
  12. package/dist/es2019/pm-plugins/utils/layout-column-selection.js +4 -1
  13. package/dist/es2019/ui/LayoutColumnMenu/DistributeColumnsDropdownItem.js +4 -10
  14. package/dist/es2019/ui/LayoutColumnMenu/VerticalAlignDropdownItem.js +2 -1
  15. package/dist/es2019/ui/LayoutColumnMenu/VerticalAlignNestedMenu.js +2 -5
  16. package/dist/es2019/ui/LayoutColumnMenu/components.js +11 -2
  17. package/dist/es2019/ui/LayoutColumnMenu/index.js +5 -33
  18. package/dist/esm/pm-plugins/actions.js +31 -26
  19. package/dist/esm/pm-plugins/utils/{redistribute-proportionally.js → layout-column-distribution.js} +37 -3
  20. package/dist/esm/pm-plugins/utils/layout-column-selection.js +4 -3
  21. package/dist/esm/ui/LayoutColumnMenu/DistributeColumnsDropdownItem.js +4 -14
  22. package/dist/esm/ui/LayoutColumnMenu/VerticalAlignDropdownItem.js +2 -1
  23. package/dist/esm/ui/LayoutColumnMenu/VerticalAlignNestedMenu.js +2 -5
  24. package/dist/esm/ui/LayoutColumnMenu/components.js +16 -8
  25. package/dist/esm/ui/LayoutColumnMenu/index.js +5 -37
  26. package/dist/types/pm-plugins/actions.d.ts +4 -2
  27. package/dist/types/pm-plugins/utils/layout-column-distribution.d.ts +16 -0
  28. package/dist/types/pm-plugins/utils/layout-column-selection.d.ts +1 -1
  29. package/dist/types/ui/LayoutColumnMenu/VerticalAlignDropdownItem.d.ts +4 -6
  30. package/dist/types/ui/LayoutColumnMenu/verticalAlignIcons.d.ts +1 -1
  31. package/dist/types-ts4.5/pm-plugins/actions.d.ts +4 -2
  32. package/dist/types-ts4.5/pm-plugins/utils/layout-column-distribution.d.ts +16 -0
  33. package/dist/types-ts4.5/pm-plugins/utils/layout-column-selection.d.ts +1 -1
  34. package/dist/types-ts4.5/ui/LayoutColumnMenu/VerticalAlignDropdownItem.d.ts +4 -6
  35. package/dist/types-ts4.5/ui/LayoutColumnMenu/verticalAlignIcons.d.ts +1 -1
  36. package/package.json +6 -6
  37. package/dist/types/pm-plugins/utils/redistribute-proportionally.d.ts +0 -2
  38. package/dist/types-ts4.5/pm-plugins/utils/redistribute-proportionally.d.ts +0 -2
@@ -2,6 +2,7 @@ import React, { useCallback, useMemo } from 'react';
2
2
  import { useIntl } from 'react-intl';
3
3
  import { layoutMessages } from '@atlaskit/editor-common/messages';
4
4
  import { ToolbarDropdownItem } from '@atlaskit/editor-toolbar';
5
+ import { isDistributedUniformly } from '../../pm-plugins/utils/layout-column-distribution';
5
6
  import { useSelectedLayoutColumns } from './useSelectedLayoutColumns';
6
7
  export const DistributeColumnsDropdownItem = ({
7
8
  api
@@ -26,25 +27,18 @@ export const DistributeColumnsDropdownItem = ({
26
27
  return closedTr !== null && closedTr !== void 0 ? closedTr : tr;
27
28
  });
28
29
  }, [api]);
29
-
30
- // Hide when selected columns are already evenly distributed — no-op action.
31
- // Must be before any early return to satisfy rules-of-hooks.
32
30
  const isAlreadyUniform = useMemo(() => {
33
31
  if (!selectedLayoutColumns || selectedLayoutColumns.selectedColumns.length < 2) {
34
32
  return false;
35
33
  }
36
34
  const selectedWidths = selectedLayoutColumns.selectedColumns.map(col => col.node.attrs.width);
37
- const selectedTotal = selectedWidths.reduce((sum, w) => sum + w, 0);
38
- const equalWidth = Number((selectedTotal / selectedWidths.length).toFixed(2));
39
- return selectedWidths.every(w => w === equalWidth);
35
+ return isDistributedUniformly(selectedWidths);
40
36
  }, [selectedLayoutColumns]);
41
37
  if (selectedLayoutColumns === undefined || selectedLayoutColumns.selectedColumns.length < 2) {
42
38
  return null;
43
39
  }
44
- if (isAlreadyUniform) {
45
- return null;
46
- }
47
40
  return /*#__PURE__*/React.createElement(ToolbarDropdownItem, {
48
- onClick: handleClick
41
+ onClick: handleClick,
42
+ isDisabled: isAlreadyUniform
49
43
  }, formatMessage(layoutMessages.distributeColumns));
50
44
  };
@@ -28,6 +28,7 @@ export const VerticalAlignDropdownItem = ({
28
28
  size: "small"
29
29
  }),
30
30
  isSelected: isSelected,
31
- onClick: onClick
31
+ onClick: onClick,
32
+ role: "menuitemradio"
32
33
  }, formatMessage(label));
33
34
  };
@@ -1,7 +1,7 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { useIntl } from 'react-intl';
3
3
  import { layoutMessages } from '@atlaskit/editor-common/messages';
4
- import { LayoutIcon, NestedDropdownRightIcon, ToolbarNestedDropdownMenu } from '@atlaskit/editor-toolbar';
4
+ import { NestedDropdownRightIcon, ToolbarNestedDropdownMenu } from '@atlaskit/editor-toolbar';
5
5
  import { getLayoutColumnValign } from '../../pm-plugins/utils/layout-column-selection';
6
6
  import { useSelectedLayoutColumns } from './useSelectedLayoutColumns';
7
7
  import { VERTICAL_ALIGN_ICONS } from './verticalAlignIcons';
@@ -24,10 +24,7 @@ export const VerticalAlignNestedMenu = ({
24
24
  }
25
25
  return firstValign;
26
26
  }, [selectedLayoutColumns]);
27
- const TriggerIcon = currentValign ? VERTICAL_ALIGN_ICONS[currentValign] : LayoutIcon;
28
- if (!selectedLayoutColumns) {
29
- return null;
30
- }
27
+ const TriggerIcon = currentValign ? VERTICAL_ALIGN_ICONS[currentValign] : VERTICAL_ALIGN_ICONS.top;
31
28
  return /*#__PURE__*/React.createElement(ToolbarNestedDropdownMenu, {
32
29
  elemBefore: /*#__PURE__*/React.createElement(TriggerIcon, {
33
30
  label: "",
@@ -1,5 +1,6 @@
1
- import React from 'react';
1
+ import React, { useContext } from 'react';
2
2
  import { layoutMessages } from '@atlaskit/editor-common/messages';
3
+ import { OutsideClickTargetRefContext } from '@atlaskit/editor-common/ui-react';
3
4
  import { ToolbarDropdownItemSection } from '@atlaskit/editor-toolbar';
4
5
  import { ToolbarMenuContainer } from '@atlaskit/editor-toolbar/toolbar-menu-container';
5
6
  import { DeleteColumnDropdownItem } from './DeleteColumnDropdownItem';
@@ -8,6 +9,14 @@ import { InsertColumnDropdownItem } from './InsertColumnDropdownItem';
8
9
  import { DELETE_COLUMN_MENU_ITEM, DISTRIBUTE_COLUMNS_MENU_ITEM, INSERT_COLUMN_LEFT_MENU_ITEM, INSERT_COLUMN_RIGHT_MENU_ITEM, LAYOUT_COLUMN_MENU, LAYOUT_COLUMN_MENU_RANK, LAYOUT_COLUMN_MENU_SECTION, LAYOUT_COLUMN_MENU_SECTION_RANK, LAYOUT_COLUMN_VERTICAL_ALIGN_MENU_SECTION, VERTICAL_ALIGN_BOTTOM_MENU_ITEM, VERTICAL_ALIGN_MENU, VERTICAL_ALIGN_MENU_RANK, VERTICAL_ALIGN_MENU_SECTION_RANK, VERTICAL_ALIGN_MIDDLE_MENU_ITEM, VERTICAL_ALIGN_TOP_MENU_ITEM } from './keys';
9
10
  import { VerticalAlignDropdownItem } from './VerticalAlignDropdownItem';
10
11
  import { VerticalAlignNestedMenu } from './VerticalAlignNestedMenu';
12
+ const LayoutColumnMenuContainer = ({
13
+ children
14
+ }) => {
15
+ const setOutsideClickTargetRef = useContext(OutsideClickTargetRefContext);
16
+ return /*#__PURE__*/React.createElement(ToolbarMenuContainer, {
17
+ ref: setOutsideClickTargetRef
18
+ }, children);
19
+ };
11
20
  export const LAYOUT_COLUMN_MENU_FALLBACKS = {
12
21
  'menu-section': ({
13
22
  children
@@ -18,7 +27,7 @@ export const getLayoutColumnMenuComponents = ({
18
27
  }) => {
19
28
  return [{
20
29
  ...LAYOUT_COLUMN_MENU,
21
- component: ToolbarMenuContainer
30
+ component: LayoutColumnMenuContainer
22
31
  }, {
23
32
  ...LAYOUT_COLUMN_MENU_SECTION,
24
33
  parents: [{
@@ -1,9 +1,8 @@
1
- import React, { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
1
+ import React, { useCallback, useEffect, useMemo } from 'react';
2
2
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
3
3
  import { DRAG_HANDLE_SELECTOR } from '@atlaskit/editor-common/styles';
4
- import { EditorToolbarProvider } from '@atlaskit/editor-common/toolbar';
5
4
  import { Popup } from '@atlaskit/editor-common/ui';
6
- import { OutsideClickTargetRefContext, withReactEditorViewOuterListeners } from '@atlaskit/editor-common/ui-react';
5
+ import { withReactEditorViewOuterListeners } from '@atlaskit/editor-common/ui-react';
7
6
  import { akEditorFloatingOverlapPanelZIndex } from '@atlaskit/editor-shared-styles';
8
7
  import { ToolbarDropdownMenuProvider } from '@atlaskit/editor-toolbar';
9
8
  import { SurfaceRenderer } from '@atlaskit/editor-ui-control-model';
@@ -11,8 +10,8 @@ import { getLayoutColumnMenuAnchorPos } from '../../pm-plugins/utils/layout-colu
11
10
  import { LAYOUT_COLUMN_MENU_FALLBACKS } from './components';
12
11
  import { LAYOUT_COLUMN_MENU } from './keys';
13
12
  const PopupWithListeners = withReactEditorViewOuterListeners(Popup);
14
- const FALLBACK_MENU_HEIGHT = 300;
15
13
  const LAYOUT_COLUMN_MENU_POPUP_OFFSET = [0, 4];
14
+ const TOOLBAR_MENU_SELECTOR = '[data-toolbar-component="menu"]';
16
15
 
17
16
  /**
18
17
  * Returns the drag handle button for the selected layout column.
@@ -50,17 +49,6 @@ export const LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMen
50
49
  selection: (_states$selectionStat = states.selectionState) === null || _states$selectionStat === void 0 ? void 0 : _states$selectionStat.selection
51
50
  };
52
51
  });
53
- const setOutsideClickTargetRef = useContext(OutsideClickTargetRefContext);
54
- const menuRef = useRef(null);
55
- const popupRef = useRef(undefined);
56
- const [menuHeight, setMenuHeight] = React.useState(FALLBACK_MENU_HEIGHT);
57
- useLayoutEffect(() => {
58
- var _popupRef$current;
59
- if (!isLayoutColumnMenuOpen) {
60
- return;
61
- }
62
- setMenuHeight(((_popupRef$current = popupRef.current) === null || _popupRef$current === void 0 ? void 0 : _popupRef$current.clientHeight) || FALLBACK_MENU_HEIGHT);
63
- }, [isLayoutColumnMenuOpen]);
64
52
  const closeLayoutColumnMenu = useCallback(() => {
65
53
  var _api$core, _api$layout;
66
54
  api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(api === null || api === void 0 ? void 0 : (_api$layout = api.layout) === null || _api$layout === void 0 ? void 0 : _api$layout.commands.toggleLayoutColumnMenu({
@@ -68,11 +56,7 @@ export const LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMen
68
56
  }));
69
57
  }, [api]);
70
58
  const handleClickOutside = useCallback(event => {
71
- var _menuRef$current;
72
- if (event.target instanceof Node && (_menuRef$current = menuRef.current) !== null && _menuRef$current !== void 0 && _menuRef$current.contains(event.target)) {
73
- return;
74
- }
75
- if (event.target instanceof Element && event.target.closest('[data-toolbar-nested-dropdown-menu]')) {
59
+ if (event.target instanceof Element && (event.target.closest(TOOLBAR_MENU_SELECTOR) || event.target.closest('[data-toolbar-nested-dropdown-menu]'))) {
76
60
  return;
77
61
  }
78
62
 
@@ -89,13 +73,6 @@ export const LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMen
89
73
  closeLayoutColumnMenu();
90
74
  }
91
75
  }, [closeLayoutColumnMenu]);
92
- const handleMenuRef = useCallback(el => {
93
- setOutsideClickTargetRef === null || setOutsideClickTargetRef === void 0 ? void 0 : setOutsideClickTargetRef(el);
94
- menuRef.current = el;
95
- if (el) {
96
- popupRef.current = el;
97
- }
98
- }, [setOutsideClickTargetRef]);
99
76
  const components = (_api$uiControlRegistr = api === null || api === void 0 ? void 0 : (_api$uiControlRegistr2 = api.uiControlRegistry) === null || _api$uiControlRegistr2 === void 0 ? void 0 : _api$uiControlRegistr2.actions.getComponents(LAYOUT_COLUMN_MENU.key)) !== null && _api$uiControlRegistr !== void 0 ? _api$uiControlRegistr : [];
100
77
  const target = useMemo(() => isLayoutColumnMenuOpen ? getLayoutColumnMenuTarget(editorView, selection, layoutColumnMenuAnchorPos) : null, [editorView, isLayoutColumnMenuOpen, layoutColumnMenuAnchorPos, selection]);
101
78
  const hasValidTarget = target instanceof HTMLElement;
@@ -114,16 +91,11 @@ export const LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMen
114
91
  scrollableElement: scrollableElement,
115
92
  zIndex: akEditorFloatingOverlapPanelZIndex,
116
93
  alignX: "center",
117
- fitHeight: menuHeight,
118
94
  preventOverflow: true,
119
95
  stick: true,
120
96
  offset: LAYOUT_COLUMN_MENU_POPUP_OFFSET,
121
97
  handleClickOutside: handleClickOutside,
122
98
  handleEscapeKeydown: closeLayoutColumnMenu
123
- }, /*#__PURE__*/React.createElement("div", {
124
- ref: handleMenuRef
125
- }, /*#__PURE__*/React.createElement(EditorToolbarProvider, {
126
- editorView: editorView
127
99
  }, /*#__PURE__*/React.createElement(ToolbarDropdownMenuProvider, {
128
100
  isOpen: isLayoutColumnMenuOpen,
129
101
  setIsOpen: handleSetIsOpen
@@ -131,5 +103,5 @@ export const LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMen
131
103
  components: components,
132
104
  fallbacks: LAYOUT_COLUMN_MENU_FALLBACKS,
133
105
  surface: LAYOUT_COLUMN_MENU
134
- })))));
106
+ })));
135
107
  });
@@ -12,8 +12,8 @@ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equ
12
12
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
13
13
  import { EVEN_DISTRIBUTED_COL_WIDTHS, MAX_LAYOUT_COLUMNS, MAX_STANDARD_LAYOUT_COLUMNS, MIN_LAYOUT_COLUMN_WIDTH_PERCENT } from './consts';
14
14
  import { pluginKey } from './plugin-key';
15
+ import { calculateDistribution, isDistributedUniformly, redistributeAfterDeletion, redistributeProportionally } from './utils/layout-column-distribution';
15
16
  import { getSelectedLayoutColumns } from './utils/layout-column-selection';
16
- import { redistributeAfterDeletion, redistributeProportionally } from './utils/redistribute-proportionally';
17
17
  export var ONE_COL_LAYOUTS = ['single'];
18
18
  export var TWO_COL_LAYOUTS = ['two_equal', 'two_left_sidebar', 'two_right_sidebar'];
19
19
  export var THREE_COL_LAYOUTS = ['three_equal', 'three_with_sidebars'];
@@ -677,31 +677,30 @@ export var distributeLayoutColumns = function distributeLayoutColumns(editorAnal
677
677
  return null;
678
678
  }
679
679
  var layoutSectionNode = selectedLayoutColumns.layoutSectionNode,
680
- layoutSectionPos = selectedLayoutColumns.layoutSectionPos,
681
680
  selectedColumnIndices = selectedLayoutColumns.selectedColumnIndices,
682
681
  selectedColumns = selectedLayoutColumns.selectedColumns;
683
682
  var endIndex = selectedColumnIndices[selectedColumnIndices.length - 1];
684
683
  var selectedColumnCount = selectedColumns.length;
685
684
  var totalColumnCount = layoutSectionNode.childCount;
686
-
687
- // Compute equal width for selected columns
688
685
  var existingWidths = mapChildren(layoutSectionNode, function (column) {
689
686
  return column.attrs.width;
690
687
  });
691
- var selectedTotal = selectedColumnIndices.reduce(function (sum, idx) {
692
- return sum + existingWidths[idx];
693
- }, 0);
694
- var equalWidth = selectedTotal / selectedColumnCount;
695
-
696
- // Early return if selected columns are already uniformly distributed — avoids spurious undo entry.
697
- if (selectedColumnIndices.every(function (idx) {
698
- return existingWidths[idx] === Number(equalWidth.toFixed(2));
699
- })) {
688
+ var selectedWidths = selectedColumns.map(function (_ref9) {
689
+ var node = _ref9.node;
690
+ return node.attrs.width;
691
+ });
692
+ var distribution = calculateDistribution(selectedWidths);
693
+ if (!distribution) {
694
+ return null;
695
+ }
696
+ var selectedTotal = distribution.selectedTotal,
697
+ equalWidth = distribution.equalWidth;
698
+ if (isDistributedUniformly(selectedWidths, distribution)) {
700
699
  return null;
701
700
  }
702
701
 
703
702
  // Build new widths array: selected columns get equal share, unselected unchanged.
704
- // Assign truncated (2dp) equal widths to all selected cols except the last, which absorbs
703
+ // Assign rounded (2dp) equal widths to all selected cols except the last, which absorbs
705
704
  // the rounding remainder so the sum of selected widths equals selectedTotal exactly.
706
705
  var selectedIndexSet = new Set(selectedColumnIndices);
707
706
  var assignedToSelected = 0;
@@ -712,16 +711,22 @@ export var distributeLayoutColumns = function distributeLayoutColumns(editorAnal
712
711
  }
713
712
  selectedAssignedCount += 1;
714
713
  if (selectedAssignedCount < selectedColumnCount) {
715
- var truncated = Number(equalWidth.toFixed(2));
716
- assignedToSelected += truncated;
717
- return truncated;
714
+ assignedToSelected += equalWidth;
715
+ return equalWidth;
718
716
  }
719
717
  // Last selected column: absorb the remainder to avoid drift
720
718
  return Number((selectedTotal - assignedToSelected).toFixed(2));
721
719
  });
722
720
 
723
- // Apply widths via replaceWith (mirroring forceColumnWidths approach)
724
- tr.replaceWith(layoutSectionPos + 1, layoutSectionPos + layoutSectionNode.nodeSize - 1, columnWidth(layoutSectionNode, tr.doc.type.schema, newWidths));
721
+ // Apply widths via setNodeMarkup per selected column — keeps nodes in place (preserves identity, marks, decorations)
722
+ selectedColumns.forEach(function (_ref0, i) {
723
+ var node = _ref0.node,
724
+ pos = _ref0.pos;
725
+ var colIdx = selectedColumnIndices[i];
726
+ tr.setNodeMarkup(pos, node.type, _objectSpread(_objectSpread({}, node.attrs), {}, {
727
+ width: newWidths[colIdx]
728
+ }));
729
+ });
725
730
  editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
726
731
  action: ACTION.UPDATED,
727
732
  actionSubject: ACTION_SUBJECT.DOCUMENT,
@@ -739,11 +744,11 @@ export var distributeLayoutColumns = function distributeLayoutColumns(editorAnal
739
744
  return tr;
740
745
  };
741
746
  };
742
- export var toggleLayoutColumnMenu = function toggleLayoutColumnMenu(_ref9) {
743
- var anchorPos = _ref9.anchorPos,
744
- isOpen = _ref9.isOpen;
745
- return function (_ref0) {
746
- var tr = _ref0.tr;
747
+ export var toggleLayoutColumnMenu = function toggleLayoutColumnMenu(_ref1) {
748
+ var anchorPos = _ref1.anchorPos,
749
+ isOpen = _ref1.isOpen;
750
+ return function (_ref10) {
751
+ var tr = _ref10.tr;
747
752
  tr.setMeta('toggleLayoutColumnMenu', {
748
753
  anchorPos: anchorPos,
749
754
  isOpen: isOpen
@@ -753,8 +758,8 @@ export var toggleLayoutColumnMenu = function toggleLayoutColumnMenu(_ref9) {
753
758
  };
754
759
  };
755
760
  export var deleteLayoutColumn = function deleteLayoutColumn(editorAnalyticsAPI) {
756
- return function (_ref1) {
757
- var tr = _ref1.tr;
761
+ return function (_ref11) {
762
+ var tr = _ref11.tr;
758
763
  if (!expValEqualsNoExposure('platform_editor_layout_column_menu', 'isEnabled', true)) {
759
764
  return null;
760
765
  }
@@ -7,9 +7,6 @@ var sumWidths = function sumWidths(widths) {
7
7
  return sum + width;
8
8
  }, 0);
9
9
  };
10
- var isValidWidth = function isValidWidth(width) {
11
- return Number.isFinite(width) && width > 0;
12
- };
13
10
  var normaliseWidthsTotal = function normaliseWidthsTotal(widths, totalWidth, minWidth) {
14
11
  var roundedWidths = widths.map(roundLayoutColumnWidth);
15
12
  var remainder = roundLayoutColumnWidth(totalWidth - sumWidths(roundedWidths));
@@ -30,6 +27,9 @@ var normaliseWidthsTotal = function normaliseWidthsTotal(widths, totalWidth, min
30
27
  return index === adjustmentIndex ? adjustedWidth : width;
31
28
  });
32
29
  };
30
+ var isValidWidth = function isValidWidth(width) {
31
+ return Number.isFinite(width) && width > 0;
32
+ };
33
33
  var redistributeWithMinimumWidth = function redistributeWithMinimumWidth(_ref) {
34
34
  var minWidth = _ref.minWidth,
35
35
  totalWidth = _ref.totalWidth,
@@ -74,6 +74,40 @@ var redistributeWithMinimumWidth = function redistributeWithMinimumWidth(_ref) {
74
74
  });
75
75
  return widths;
76
76
  };
77
+
78
+ /**
79
+ * Returns true when the given selected columns already reflect the distribution that
80
+ * `distributeLayoutColumns` would produce — i.e. the first N-1 cols each hold
81
+ * `equalWidth` (rounded to 2 dp) and the last col absorbs the rounding remainder.
82
+ *
83
+ * This mirrors the action's "last col absorbs remainder" logic so that the UI can
84
+ * disable the option when it would be a no-op, avoiding spurious undo entries.
85
+ */
86
+
87
+ export var calculateDistribution = function calculateDistribution(selectedWidths) {
88
+ var count = selectedWidths.length;
89
+ if (count < 2) {
90
+ return undefined;
91
+ }
92
+ var selectedTotal = sumWidths(selectedWidths);
93
+ var equalWidth = roundLayoutColumnWidth(selectedTotal / count);
94
+ return {
95
+ selectedTotal: selectedTotal,
96
+ equalWidth: equalWidth
97
+ };
98
+ };
99
+ export function isDistributedUniformly(selectedWidths) {
100
+ var distribution = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : calculateDistribution(selectedWidths);
101
+ if (!distribution || selectedWidths.length < 2) {
102
+ return false;
103
+ }
104
+ var selectedTotal = distribution.selectedTotal,
105
+ equalWidth = distribution.equalWidth;
106
+ var lastColWidth = roundLayoutColumnWidth(selectedTotal - equalWidth * (selectedWidths.length - 1));
107
+ return selectedWidths.slice(0, -1).every(function (width) {
108
+ return width === equalWidth;
109
+ }) && selectedWidths[selectedWidths.length - 1] === lastColWidth;
110
+ }
77
111
  export var redistributeAfterDeletion = function redistributeAfterDeletion(currentWidths, removeIndex, minWidth) {
78
112
  if (currentWidths.length === 0 || removeIndex < 0 || removeIndex >= currentWidths.length || !isValidWidth(minWidth)) {
79
113
  return currentWidths;
@@ -106,7 +106,8 @@ export var getLayoutSectionColumnCount = function getLayoutSectionColumnCount(la
106
106
  return (layoutSection === null || layoutSection === void 0 ? void 0 : layoutSection.type.name) === 'layoutSection' ? layoutSection.childCount : 0;
107
107
  };
108
108
  export var getLayoutColumnValign = function getLayoutColumnValign(layoutColumn) {
109
- return layoutColumn === null || layoutColumn === void 0 ? void 0 : layoutColumn.attrs.valign;
109
+ var _ref2;
110
+ return layoutColumn ? (_ref2 = layoutColumn.attrs.valign) !== null && _ref2 !== void 0 ? _ref2 : 'top' : undefined;
110
111
  };
111
112
  export var getLayoutColumnMenuAnchorPos = function getLayoutColumnMenuAnchorPos(selection, anchorPosFromHandle) {
112
113
  var _clickedSelectedColum, _selectedLayoutColumn;
@@ -114,8 +115,8 @@ export var getLayoutColumnMenuAnchorPos = function getLayoutColumnMenuAnchorPos(
114
115
  if (!selectedLayoutColumns) {
115
116
  return undefined;
116
117
  }
117
- var clickedSelectedColumn = selectedLayoutColumns.selectedColumns.find(function (_ref2) {
118
- var pos = _ref2.pos;
118
+ var clickedSelectedColumn = selectedLayoutColumns.selectedColumns.find(function (_ref3) {
119
+ var pos = _ref3.pos;
119
120
  return pos === anchorPosFromHandle;
120
121
  });
121
122
  return (_clickedSelectedColum = clickedSelectedColumn === null || clickedSelectedColumn === void 0 ? void 0 : clickedSelectedColumn.pos) !== null && _clickedSelectedColum !== void 0 ? _clickedSelectedColum : (_selectedLayoutColumn = selectedLayoutColumns.selectedColumns[0]) === null || _selectedLayoutColumn === void 0 ? void 0 : _selectedLayoutColumn.pos;
@@ -2,6 +2,7 @@ import React, { useCallback, useMemo } from 'react';
2
2
  import { useIntl } from 'react-intl';
3
3
  import { layoutMessages } from '@atlaskit/editor-common/messages';
4
4
  import { ToolbarDropdownItem } from '@atlaskit/editor-toolbar';
5
+ import { isDistributedUniformly } from '../../pm-plugins/utils/layout-column-distribution';
5
6
  import { useSelectedLayoutColumns } from './useSelectedLayoutColumns';
6
7
  export var DistributeColumnsDropdownItem = function DistributeColumnsDropdownItem(_ref) {
7
8
  var api = _ref.api;
@@ -24,9 +25,6 @@ export var DistributeColumnsDropdownItem = function DistributeColumnsDropdownIte
24
25
  return closedTr !== null && closedTr !== void 0 ? closedTr : tr;
25
26
  });
26
27
  }, [api]);
27
-
28
- // Hide when selected columns are already evenly distributed — no-op action.
29
- // Must be before any early return to satisfy rules-of-hooks.
30
28
  var isAlreadyUniform = useMemo(function () {
31
29
  if (!selectedLayoutColumns || selectedLayoutColumns.selectedColumns.length < 2) {
32
30
  return false;
@@ -34,21 +32,13 @@ export var DistributeColumnsDropdownItem = function DistributeColumnsDropdownIte
34
32
  var selectedWidths = selectedLayoutColumns.selectedColumns.map(function (col) {
35
33
  return col.node.attrs.width;
36
34
  });
37
- var selectedTotal = selectedWidths.reduce(function (sum, w) {
38
- return sum + w;
39
- }, 0);
40
- var equalWidth = Number((selectedTotal / selectedWidths.length).toFixed(2));
41
- return selectedWidths.every(function (w) {
42
- return w === equalWidth;
43
- });
35
+ return isDistributedUniformly(selectedWidths);
44
36
  }, [selectedLayoutColumns]);
45
37
  if (selectedLayoutColumns === undefined || selectedLayoutColumns.selectedColumns.length < 2) {
46
38
  return null;
47
39
  }
48
- if (isAlreadyUniform) {
49
- return null;
50
- }
51
40
  return /*#__PURE__*/React.createElement(ToolbarDropdownItem, {
52
- onClick: handleClick
41
+ onClick: handleClick,
42
+ isDisabled: isAlreadyUniform
53
43
  }, formatMessage(layoutMessages.distributeColumns));
54
44
  };
@@ -27,6 +27,7 @@ export var VerticalAlignDropdownItem = function VerticalAlignDropdownItem(_ref)
27
27
  size: "small"
28
28
  }),
29
29
  isSelected: isSelected,
30
- onClick: onClick
30
+ onClick: onClick,
31
+ role: "menuitemradio"
31
32
  }, formatMessage(label));
32
33
  };
@@ -1,7 +1,7 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { useIntl } from 'react-intl';
3
3
  import { layoutMessages } from '@atlaskit/editor-common/messages';
4
- import { LayoutIcon, NestedDropdownRightIcon, ToolbarNestedDropdownMenu } from '@atlaskit/editor-toolbar';
4
+ import { NestedDropdownRightIcon, ToolbarNestedDropdownMenu } from '@atlaskit/editor-toolbar';
5
5
  import { getLayoutColumnValign } from '../../pm-plugins/utils/layout-column-selection';
6
6
  import { useSelectedLayoutColumns } from './useSelectedLayoutColumns';
7
7
  import { VERTICAL_ALIGN_ICONS } from './verticalAlignIcons';
@@ -23,10 +23,7 @@ export var VerticalAlignNestedMenu = function VerticalAlignNestedMenu(_ref) {
23
23
  }
24
24
  return firstValign;
25
25
  }, [selectedLayoutColumns]);
26
- var TriggerIcon = currentValign ? VERTICAL_ALIGN_ICONS[currentValign] : LayoutIcon;
27
- if (!selectedLayoutColumns) {
28
- return null;
29
- }
26
+ var TriggerIcon = currentValign ? VERTICAL_ALIGN_ICONS[currentValign] : VERTICAL_ALIGN_ICONS.top;
30
27
  return /*#__PURE__*/React.createElement(ToolbarNestedDropdownMenu, {
31
28
  elemBefore: /*#__PURE__*/React.createElement(TriggerIcon, {
32
29
  label: "",
@@ -1,8 +1,9 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
3
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
- import React from 'react';
4
+ import React, { useContext } from 'react';
5
5
  import { layoutMessages } from '@atlaskit/editor-common/messages';
6
+ import { OutsideClickTargetRefContext } from '@atlaskit/editor-common/ui-react';
6
7
  import { ToolbarDropdownItemSection } from '@atlaskit/editor-toolbar';
7
8
  import { ToolbarMenuContainer } from '@atlaskit/editor-toolbar/toolbar-menu-container';
8
9
  import { DeleteColumnDropdownItem } from './DeleteColumnDropdownItem';
@@ -11,16 +12,23 @@ import { InsertColumnDropdownItem } from './InsertColumnDropdownItem';
11
12
  import { DELETE_COLUMN_MENU_ITEM, DISTRIBUTE_COLUMNS_MENU_ITEM, INSERT_COLUMN_LEFT_MENU_ITEM, INSERT_COLUMN_RIGHT_MENU_ITEM, LAYOUT_COLUMN_MENU, LAYOUT_COLUMN_MENU_RANK, LAYOUT_COLUMN_MENU_SECTION, LAYOUT_COLUMN_MENU_SECTION_RANK, LAYOUT_COLUMN_VERTICAL_ALIGN_MENU_SECTION, VERTICAL_ALIGN_BOTTOM_MENU_ITEM, VERTICAL_ALIGN_MENU, VERTICAL_ALIGN_MENU_RANK, VERTICAL_ALIGN_MENU_SECTION_RANK, VERTICAL_ALIGN_MIDDLE_MENU_ITEM, VERTICAL_ALIGN_TOP_MENU_ITEM } from './keys';
12
13
  import { VerticalAlignDropdownItem } from './VerticalAlignDropdownItem';
13
14
  import { VerticalAlignNestedMenu } from './VerticalAlignNestedMenu';
15
+ var LayoutColumnMenuContainer = function LayoutColumnMenuContainer(_ref) {
16
+ var children = _ref.children;
17
+ var setOutsideClickTargetRef = useContext(OutsideClickTargetRefContext);
18
+ return /*#__PURE__*/React.createElement(ToolbarMenuContainer, {
19
+ ref: setOutsideClickTargetRef
20
+ }, children);
21
+ };
14
22
  export var LAYOUT_COLUMN_MENU_FALLBACKS = {
15
- 'menu-section': function menuSection(_ref) {
16
- var children = _ref.children;
23
+ 'menu-section': function menuSection(_ref2) {
24
+ var children = _ref2.children;
17
25
  return /*#__PURE__*/React.createElement(ToolbarDropdownItemSection, null, children);
18
26
  }
19
27
  };
20
- export var getLayoutColumnMenuComponents = function getLayoutColumnMenuComponents(_ref2) {
21
- var api = _ref2.api;
28
+ export var getLayoutColumnMenuComponents = function getLayoutColumnMenuComponents(_ref3) {
29
+ var api = _ref3.api;
22
30
  return [_objectSpread(_objectSpread({}, LAYOUT_COLUMN_MENU), {}, {
23
- component: ToolbarMenuContainer
31
+ component: LayoutColumnMenuContainer
24
32
  }), _objectSpread(_objectSpread({}, LAYOUT_COLUMN_MENU_SECTION), {}, {
25
33
  parents: [_objectSpread(_objectSpread({}, LAYOUT_COLUMN_MENU), {}, {
26
34
  rank: LAYOUT_COLUMN_MENU_RANK[LAYOUT_COLUMN_MENU_SECTION.key]
@@ -64,8 +72,8 @@ export var getLayoutColumnMenuComponents = function getLayoutColumnMenuComponent
64
72
  rank: LAYOUT_COLUMN_MENU_SECTION_RANK[DELETE_COLUMN_MENU_ITEM.key]
65
73
  })]
66
74
  }), _objectSpread(_objectSpread({}, VERTICAL_ALIGN_MENU), {}, {
67
- component: function component(_ref3) {
68
- var children = _ref3.children;
75
+ component: function component(_ref4) {
76
+ var children = _ref4.children;
69
77
  return /*#__PURE__*/React.createElement(VerticalAlignNestedMenu, {
70
78
  api: api
71
79
  }, children);
@@ -1,10 +1,8 @@
1
- import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
- import React, { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
1
+ import React, { useCallback, useEffect, useMemo } from 'react';
3
2
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
4
3
  import { DRAG_HANDLE_SELECTOR } from '@atlaskit/editor-common/styles';
5
- import { EditorToolbarProvider } from '@atlaskit/editor-common/toolbar';
6
4
  import { Popup } from '@atlaskit/editor-common/ui';
7
- import { OutsideClickTargetRefContext, withReactEditorViewOuterListeners } from '@atlaskit/editor-common/ui-react';
5
+ import { withReactEditorViewOuterListeners } from '@atlaskit/editor-common/ui-react';
8
6
  import { akEditorFloatingOverlapPanelZIndex } from '@atlaskit/editor-shared-styles';
9
7
  import { ToolbarDropdownMenuProvider } from '@atlaskit/editor-toolbar';
10
8
  import { SurfaceRenderer } from '@atlaskit/editor-ui-control-model';
@@ -12,8 +10,8 @@ import { getLayoutColumnMenuAnchorPos } from '../../pm-plugins/utils/layout-colu
12
10
  import { LAYOUT_COLUMN_MENU_FALLBACKS } from './components';
13
11
  import { LAYOUT_COLUMN_MENU } from './keys';
14
12
  var PopupWithListeners = withReactEditorViewOuterListeners(Popup);
15
- var FALLBACK_MENU_HEIGHT = 300;
16
13
  var LAYOUT_COLUMN_MENU_POPUP_OFFSET = [0, 4];
14
+ var TOOLBAR_MENU_SELECTOR = '[data-toolbar-component="menu"]';
17
15
 
18
16
  /**
19
17
  * Returns the drag handle button for the selected layout column.
@@ -49,20 +47,6 @@ export var LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMenu(
49
47
  isLayoutColumnMenuOpen = _useSharedPluginState.isLayoutColumnMenuOpen,
50
48
  layoutColumnMenuAnchorPos = _useSharedPluginState.layoutColumnMenuAnchorPos,
51
49
  selection = _useSharedPluginState.selection;
52
- var setOutsideClickTargetRef = useContext(OutsideClickTargetRefContext);
53
- var menuRef = useRef(null);
54
- var popupRef = useRef(undefined);
55
- var _React$useState = React.useState(FALLBACK_MENU_HEIGHT),
56
- _React$useState2 = _slicedToArray(_React$useState, 2),
57
- menuHeight = _React$useState2[0],
58
- setMenuHeight = _React$useState2[1];
59
- useLayoutEffect(function () {
60
- var _popupRef$current;
61
- if (!isLayoutColumnMenuOpen) {
62
- return;
63
- }
64
- setMenuHeight(((_popupRef$current = popupRef.current) === null || _popupRef$current === void 0 ? void 0 : _popupRef$current.clientHeight) || FALLBACK_MENU_HEIGHT);
65
- }, [isLayoutColumnMenuOpen]);
66
50
  var closeLayoutColumnMenu = useCallback(function () {
67
51
  var _api$core, _api$layout;
68
52
  api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(api === null || api === void 0 || (_api$layout = api.layout) === null || _api$layout === void 0 ? void 0 : _api$layout.commands.toggleLayoutColumnMenu({
@@ -70,11 +54,7 @@ export var LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMenu(
70
54
  }));
71
55
  }, [api]);
72
56
  var handleClickOutside = useCallback(function (event) {
73
- var _menuRef$current;
74
- if (event.target instanceof Node && (_menuRef$current = menuRef.current) !== null && _menuRef$current !== void 0 && _menuRef$current.contains(event.target)) {
75
- return;
76
- }
77
- if (event.target instanceof Element && event.target.closest('[data-toolbar-nested-dropdown-menu]')) {
57
+ if (event.target instanceof Element && (event.target.closest(TOOLBAR_MENU_SELECTOR) || event.target.closest('[data-toolbar-nested-dropdown-menu]'))) {
78
58
  return;
79
59
  }
80
60
 
@@ -91,13 +71,6 @@ export var LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMenu(
91
71
  closeLayoutColumnMenu();
92
72
  }
93
73
  }, [closeLayoutColumnMenu]);
94
- var handleMenuRef = useCallback(function (el) {
95
- setOutsideClickTargetRef === null || setOutsideClickTargetRef === void 0 || setOutsideClickTargetRef(el);
96
- menuRef.current = el;
97
- if (el) {
98
- popupRef.current = el;
99
- }
100
- }, [setOutsideClickTargetRef]);
101
74
  var components = (_api$uiControlRegistr = api === null || api === void 0 || (_api$uiControlRegistr2 = api.uiControlRegistry) === null || _api$uiControlRegistr2 === void 0 ? void 0 : _api$uiControlRegistr2.actions.getComponents(LAYOUT_COLUMN_MENU.key)) !== null && _api$uiControlRegistr !== void 0 ? _api$uiControlRegistr : [];
102
75
  var target = useMemo(function () {
103
76
  return isLayoutColumnMenuOpen ? getLayoutColumnMenuTarget(editorView, selection, layoutColumnMenuAnchorPos) : null;
@@ -118,16 +91,11 @@ export var LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMenu(
118
91
  scrollableElement: scrollableElement,
119
92
  zIndex: akEditorFloatingOverlapPanelZIndex,
120
93
  alignX: "center",
121
- fitHeight: menuHeight,
122
94
  preventOverflow: true,
123
95
  stick: true,
124
96
  offset: LAYOUT_COLUMN_MENU_POPUP_OFFSET,
125
97
  handleClickOutside: handleClickOutside,
126
98
  handleEscapeKeydown: closeLayoutColumnMenu
127
- }, /*#__PURE__*/React.createElement("div", {
128
- ref: handleMenuRef
129
- }, /*#__PURE__*/React.createElement(EditorToolbarProvider, {
130
- editorView: editorView
131
99
  }, /*#__PURE__*/React.createElement(ToolbarDropdownMenuProvider, {
132
100
  isOpen: isLayoutColumnMenuOpen,
133
101
  setIsOpen: handleSetIsOpen
@@ -135,5 +103,5 @@ export var LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMenu(
135
103
  components: components,
136
104
  fallbacks: LAYOUT_COLUMN_MENU_FALLBACKS,
137
105
  surface: LAYOUT_COLUMN_MENU
138
- })))));
106
+ })));
139
107
  });
@@ -1,7 +1,7 @@
1
+ import type { Valign } from '@atlaskit/adf-schema/layout-column';
1
2
  import type { EditorAnalyticsAPI } from '@atlaskit/editor-common/analytics';
2
3
  import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
3
4
  import type { Command, EditorCommand, TOOLBAR_MENU_TYPE } from '@atlaskit/editor-common/types';
4
- import type { Valign } from '@atlaskit/editor-common/types/valign';
5
5
  import type { Node } from '@atlaskit/editor-prosemirror/model';
6
6
  import type { EditorState, Transaction } from '@atlaskit/editor-prosemirror/state';
7
7
  import type { Change, PresetLayout } from '../types';
@@ -17,7 +17,8 @@ export declare const getSelectedLayout: (maybeLayoutSection: Node | undefined, c
17
17
  export declare const createMultiColumnLayoutSection: (state: EditorState, numberOfColumns?: number) => Node;
18
18
  export declare const createDefaultLayoutSection: (state: EditorState) => Node;
19
19
  export declare const insertLayoutColumns: Command;
20
- export declare const insertLayoutColumnsWithAnalytics: (editorAnalyticsAPI: EditorAnalyticsAPI | undefined) => (inputMethod: TOOLBAR_MENU_TYPE) => Command;
20
+ type InsertLayoutColumnsInputMethod = TOOLBAR_MENU_TYPE | INPUT_METHOD.QUICK_INSERT | INPUT_METHOD.ELEMENT_BROWSER;
21
+ export declare const insertLayoutColumnsWithAnalytics: (editorAnalyticsAPI: EditorAnalyticsAPI | undefined) => (inputMethod: InsertLayoutColumnsInputMethod) => Command;
21
22
  /**
22
23
  * Forces a layout section node to match the given preset layout by adjusting
23
24
  * its column structure and widths, then restoring the original selection.
@@ -40,3 +41,4 @@ export declare const toggleLayoutColumnMenu: ({ anchorPos, isOpen }: {
40
41
  isOpen?: boolean;
41
42
  }) => EditorCommand;
42
43
  export declare const deleteLayoutColumn: (editorAnalyticsAPI?: EditorAnalyticsAPI) => EditorCommand;
44
+ export {};
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Returns true when the given selected columns already reflect the distribution that
3
+ * `distributeLayoutColumns` would produce — i.e. the first N-1 cols each hold
4
+ * `equalWidth` (rounded to 2 dp) and the last col absorbs the rounding remainder.
5
+ *
6
+ * This mirrors the action's "last col absorbs remainder" logic so that the UI can
7
+ * disable the option when it would be a no-op, avoiding spurious undo entries.
8
+ */
9
+ export type Distribution = {
10
+ equalWidth: number;
11
+ selectedTotal: number;
12
+ };
13
+ export declare const calculateDistribution: (selectedWidths: number[]) => Distribution | undefined;
14
+ export declare function isDistributedUniformly(selectedWidths: number[], distribution?: Distribution | undefined): boolean;
15
+ export declare const redistributeAfterDeletion: (currentWidths: number[], removeIndex: number, minWidth: number) => number[];
16
+ export declare const redistributeProportionally: (currentWidths: number[], insertIndex: number, maxColumns: number, minWidth: number) => number[];