@atlaskit/editor-plugin-layout 10.8.0 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @atlaskit/editor-plugin-layout
2
2
 
3
+ ## 10.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`2ad681c18e86f`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2ad681c18e86f) -
8
+ Add layout column menu actions for inserting, deleting, distributing, and aligning columns.
9
+ - Updated dependencies
10
+
3
11
  ## 10.8.0
4
12
 
5
13
  ### Minor Changes
@@ -20,8 +20,8 @@ var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equa
20
20
  var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
21
21
  var _consts = require("./consts");
22
22
  var _pluginKey = require("./plugin-key");
23
+ var _layoutColumnDistribution = require("./utils/layout-column-distribution");
23
24
  var _layoutColumnSelection = require("./utils/layout-column-selection");
24
- var _redistributeProportionally = require("./utils/redistribute-proportionally");
25
25
  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; }
26
26
  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) { (0, _defineProperty2.default)(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; }
27
27
  var ONE_COL_LAYOUTS = exports.ONE_COL_LAYOUTS = ['single'];
@@ -583,7 +583,7 @@ var insertLayoutColumnAt = function insertLayoutColumnAt(side, editorAnalyticsAP
583
583
  var existingWidths = (0, _utils.mapChildren)(layoutSectionNode, function (column) {
584
584
  return column.attrs.width;
585
585
  });
586
- var redistributedWidths = (0, _redistributeProportionally.redistributeProportionally)(existingWidths, insertIndex, getEffectiveMaxLayoutColumns(), _consts.MIN_LAYOUT_COLUMN_WIDTH_PERCENT);
586
+ var redistributedWidths = (0, _layoutColumnDistribution.redistributeProportionally)(existingWidths, insertIndex, getEffectiveMaxLayoutColumns(), _consts.MIN_LAYOUT_COLUMN_WIDTH_PERCENT);
587
587
  if (redistributedWidths === existingWidths) {
588
588
  return null;
589
589
  }
@@ -687,31 +687,30 @@ var distributeLayoutColumns = exports.distributeLayoutColumns = function distrib
687
687
  return null;
688
688
  }
689
689
  var layoutSectionNode = selectedLayoutColumns.layoutSectionNode,
690
- layoutSectionPos = selectedLayoutColumns.layoutSectionPos,
691
690
  selectedColumnIndices = selectedLayoutColumns.selectedColumnIndices,
692
691
  selectedColumns = selectedLayoutColumns.selectedColumns;
693
692
  var endIndex = selectedColumnIndices[selectedColumnIndices.length - 1];
694
693
  var selectedColumnCount = selectedColumns.length;
695
694
  var totalColumnCount = layoutSectionNode.childCount;
696
-
697
- // Compute equal width for selected columns
698
695
  var existingWidths = (0, _utils.mapChildren)(layoutSectionNode, function (column) {
699
696
  return column.attrs.width;
700
697
  });
701
- var selectedTotal = selectedColumnIndices.reduce(function (sum, idx) {
702
- return sum + existingWidths[idx];
703
- }, 0);
704
- var equalWidth = selectedTotal / selectedColumnCount;
705
-
706
- // Early return if selected columns are already uniformly distributed — avoids spurious undo entry.
707
- if (selectedColumnIndices.every(function (idx) {
708
- return existingWidths[idx] === Number(equalWidth.toFixed(2));
709
- })) {
698
+ var selectedWidths = selectedColumns.map(function (_ref9) {
699
+ var node = _ref9.node;
700
+ return node.attrs.width;
701
+ });
702
+ var distribution = (0, _layoutColumnDistribution.calculateDistribution)(selectedWidths);
703
+ if (!distribution) {
704
+ return null;
705
+ }
706
+ var selectedTotal = distribution.selectedTotal,
707
+ equalWidth = distribution.equalWidth;
708
+ if ((0, _layoutColumnDistribution.isDistributedUniformly)(selectedWidths, distribution)) {
710
709
  return null;
711
710
  }
712
711
 
713
712
  // Build new widths array: selected columns get equal share, unselected unchanged.
714
- // Assign truncated (2dp) equal widths to all selected cols except the last, which absorbs
713
+ // Assign rounded (2dp) equal widths to all selected cols except the last, which absorbs
715
714
  // the rounding remainder so the sum of selected widths equals selectedTotal exactly.
716
715
  var selectedIndexSet = new Set(selectedColumnIndices);
717
716
  var assignedToSelected = 0;
@@ -722,16 +721,22 @@ var distributeLayoutColumns = exports.distributeLayoutColumns = function distrib
722
721
  }
723
722
  selectedAssignedCount += 1;
724
723
  if (selectedAssignedCount < selectedColumnCount) {
725
- var truncated = Number(equalWidth.toFixed(2));
726
- assignedToSelected += truncated;
727
- return truncated;
724
+ assignedToSelected += equalWidth;
725
+ return equalWidth;
728
726
  }
729
727
  // Last selected column: absorb the remainder to avoid drift
730
728
  return Number((selectedTotal - assignedToSelected).toFixed(2));
731
729
  });
732
730
 
733
- // Apply widths via replaceWith (mirroring forceColumnWidths approach)
734
- tr.replaceWith(layoutSectionPos + 1, layoutSectionPos + layoutSectionNode.nodeSize - 1, columnWidth(layoutSectionNode, tr.doc.type.schema, newWidths));
731
+ // Apply widths via setNodeMarkup per selected column — keeps nodes in place (preserves identity, marks, decorations)
732
+ selectedColumns.forEach(function (_ref0, i) {
733
+ var node = _ref0.node,
734
+ pos = _ref0.pos;
735
+ var colIdx = selectedColumnIndices[i];
736
+ tr.setNodeMarkup(pos, node.type, _objectSpread(_objectSpread({}, node.attrs), {}, {
737
+ width: newWidths[colIdx]
738
+ }));
739
+ });
735
740
  editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
736
741
  action: _analytics.ACTION.UPDATED,
737
742
  actionSubject: _analytics.ACTION_SUBJECT.DOCUMENT,
@@ -749,11 +754,11 @@ var distributeLayoutColumns = exports.distributeLayoutColumns = function distrib
749
754
  return tr;
750
755
  };
751
756
  };
752
- var toggleLayoutColumnMenu = exports.toggleLayoutColumnMenu = function toggleLayoutColumnMenu(_ref9) {
753
- var anchorPos = _ref9.anchorPos,
754
- isOpen = _ref9.isOpen;
755
- return function (_ref0) {
756
- var tr = _ref0.tr;
757
+ var toggleLayoutColumnMenu = exports.toggleLayoutColumnMenu = function toggleLayoutColumnMenu(_ref1) {
758
+ var anchorPos = _ref1.anchorPos,
759
+ isOpen = _ref1.isOpen;
760
+ return function (_ref10) {
761
+ var tr = _ref10.tr;
757
762
  tr.setMeta('toggleLayoutColumnMenu', {
758
763
  anchorPos: anchorPos,
759
764
  isOpen: isOpen
@@ -763,8 +768,8 @@ var toggleLayoutColumnMenu = exports.toggleLayoutColumnMenu = function toggleLay
763
768
  };
764
769
  };
765
770
  var deleteLayoutColumn = exports.deleteLayoutColumn = function deleteLayoutColumn(editorAnalyticsAPI) {
766
- return function (_ref1) {
767
- var tr = _ref1.tr;
771
+ return function (_ref11) {
772
+ var tr = _ref11.tr;
768
773
  if (!(0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_layout_column_menu', 'isEnabled', true)) {
769
774
  return null;
770
775
  }
@@ -822,7 +827,7 @@ var deleteLayoutColumn = exports.deleteLayoutColumn = function deleteLayoutColum
822
827
  .sort(function (a, b) {
823
828
  return b - a;
824
829
  }).reduce(function (widths, selectedIndex) {
825
- return (0, _redistributeProportionally.redistributeAfterDeletion)(widths, selectedIndex, _consts.MIN_LAYOUT_COLUMN_WIDTH_PERCENT);
830
+ return (0, _layoutColumnDistribution.redistributeAfterDeletion)(widths, selectedIndex, _consts.MIN_LAYOUT_COLUMN_WIDTH_PERCENT);
826
831
  }, existingWidths);
827
832
  var updatedLayoutSectionNode = layoutSectionNode.copy(_model.Fragment.fromArray(remainingColumns));
828
833
  tr.replaceWith(layoutSectionPos + 1, layoutSectionPos + layoutSectionNode.nodeSize - 1, columnWidth(updatedLayoutSectionNode, tr.doc.type.schema, redistributed));
@@ -4,6 +4,8 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
+ exports.calculateDistribution = void 0;
8
+ exports.isDistributedUniformly = isDistributedUniformly;
7
9
  exports.redistributeProportionally = exports.redistributeAfterDeletion = void 0;
8
10
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
11
  var roundLayoutColumnWidth = function roundLayoutColumnWidth(width) {
@@ -14,9 +16,6 @@ var sumWidths = function sumWidths(widths) {
14
16
  return sum + width;
15
17
  }, 0);
16
18
  };
17
- var isValidWidth = function isValidWidth(width) {
18
- return Number.isFinite(width) && width > 0;
19
- };
20
19
  var normaliseWidthsTotal = function normaliseWidthsTotal(widths, totalWidth, minWidth) {
21
20
  var roundedWidths = widths.map(roundLayoutColumnWidth);
22
21
  var remainder = roundLayoutColumnWidth(totalWidth - sumWidths(roundedWidths));
@@ -37,6 +36,9 @@ var normaliseWidthsTotal = function normaliseWidthsTotal(widths, totalWidth, min
37
36
  return index === adjustmentIndex ? adjustedWidth : width;
38
37
  });
39
38
  };
39
+ var isValidWidth = function isValidWidth(width) {
40
+ return Number.isFinite(width) && width > 0;
41
+ };
40
42
  var redistributeWithMinimumWidth = function redistributeWithMinimumWidth(_ref) {
41
43
  var minWidth = _ref.minWidth,
42
44
  totalWidth = _ref.totalWidth,
@@ -81,6 +83,40 @@ var redistributeWithMinimumWidth = function redistributeWithMinimumWidth(_ref) {
81
83
  });
82
84
  return widths;
83
85
  };
86
+
87
+ /**
88
+ * Returns true when the given selected columns already reflect the distribution that
89
+ * `distributeLayoutColumns` would produce — i.e. the first N-1 cols each hold
90
+ * `equalWidth` (rounded to 2 dp) and the last col absorbs the rounding remainder.
91
+ *
92
+ * This mirrors the action's "last col absorbs remainder" logic so that the UI can
93
+ * disable the option when it would be a no-op, avoiding spurious undo entries.
94
+ */
95
+
96
+ var calculateDistribution = exports.calculateDistribution = function calculateDistribution(selectedWidths) {
97
+ var count = selectedWidths.length;
98
+ if (count < 2) {
99
+ return undefined;
100
+ }
101
+ var selectedTotal = sumWidths(selectedWidths);
102
+ var equalWidth = roundLayoutColumnWidth(selectedTotal / count);
103
+ return {
104
+ selectedTotal: selectedTotal,
105
+ equalWidth: equalWidth
106
+ };
107
+ };
108
+ function isDistributedUniformly(selectedWidths) {
109
+ var distribution = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : calculateDistribution(selectedWidths);
110
+ if (!distribution || selectedWidths.length < 2) {
111
+ return false;
112
+ }
113
+ var selectedTotal = distribution.selectedTotal,
114
+ equalWidth = distribution.equalWidth;
115
+ var lastColWidth = roundLayoutColumnWidth(selectedTotal - equalWidth * (selectedWidths.length - 1));
116
+ return selectedWidths.slice(0, -1).every(function (width) {
117
+ return width === equalWidth;
118
+ }) && selectedWidths[selectedWidths.length - 1] === lastColWidth;
119
+ }
84
120
  var redistributeAfterDeletion = exports.redistributeAfterDeletion = function redistributeAfterDeletion(currentWidths, removeIndex, minWidth) {
85
121
  if (currentWidths.length === 0 || removeIndex < 0 || removeIndex >= currentWidths.length || !isValidWidth(minWidth)) {
86
122
  return currentWidths;
@@ -9,6 +9,7 @@ var _react = _interopRequireWildcard(require("react"));
9
9
  var _reactIntl = require("react-intl");
10
10
  var _messages = require("@atlaskit/editor-common/messages");
11
11
  var _editorToolbar = require("@atlaskit/editor-toolbar");
12
+ var _layoutColumnDistribution = require("../../pm-plugins/utils/layout-column-distribution");
12
13
  var _useSelectedLayoutColumns = require("./useSelectedLayoutColumns");
13
14
  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); }
14
15
  var DistributeColumnsDropdownItem = exports.DistributeColumnsDropdownItem = function DistributeColumnsDropdownItem(_ref) {
@@ -32,9 +33,6 @@ var DistributeColumnsDropdownItem = exports.DistributeColumnsDropdownItem = func
32
33
  return closedTr !== null && closedTr !== void 0 ? closedTr : tr;
33
34
  });
34
35
  }, [api]);
35
-
36
- // Hide when selected columns are already evenly distributed — no-op action.
37
- // Must be before any early return to satisfy rules-of-hooks.
38
36
  var isAlreadyUniform = (0, _react.useMemo)(function () {
39
37
  if (!selectedLayoutColumns || selectedLayoutColumns.selectedColumns.length < 2) {
40
38
  return false;
@@ -42,21 +40,13 @@ var DistributeColumnsDropdownItem = exports.DistributeColumnsDropdownItem = func
42
40
  var selectedWidths = selectedLayoutColumns.selectedColumns.map(function (col) {
43
41
  return col.node.attrs.width;
44
42
  });
45
- var selectedTotal = selectedWidths.reduce(function (sum, w) {
46
- return sum + w;
47
- }, 0);
48
- var equalWidth = Number((selectedTotal / selectedWidths.length).toFixed(2));
49
- return selectedWidths.every(function (w) {
50
- return w === equalWidth;
51
- });
43
+ return (0, _layoutColumnDistribution.isDistributedUniformly)(selectedWidths);
52
44
  }, [selectedLayoutColumns]);
53
45
  if (selectedLayoutColumns === undefined || selectedLayoutColumns.selectedColumns.length < 2) {
54
46
  return null;
55
47
  }
56
- if (isAlreadyUniform) {
57
- return null;
58
- }
59
48
  return /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarDropdownItem, {
60
- onClick: handleClick
49
+ onClick: handleClick,
50
+ isDisabled: isAlreadyUniform
61
51
  }, formatMessage(_messages.layoutMessages.distributeColumns));
62
52
  };
@@ -9,8 +9,8 @@ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equ
9
9
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
10
10
  import { EVEN_DISTRIBUTED_COL_WIDTHS, MAX_LAYOUT_COLUMNS, MAX_STANDARD_LAYOUT_COLUMNS, MIN_LAYOUT_COLUMN_WIDTH_PERCENT } from './consts';
11
11
  import { pluginKey } from './plugin-key';
12
+ import { calculateDistribution, isDistributedUniformly, redistributeAfterDeletion, redistributeProportionally } from './utils/layout-column-distribution';
12
13
  import { getSelectedLayoutColumns } from './utils/layout-column-selection';
13
- import { redistributeAfterDeletion, redistributeProportionally } from './utils/redistribute-proportionally';
14
14
  export const ONE_COL_LAYOUTS = ['single'];
15
15
  export const TWO_COL_LAYOUTS = ['two_equal', 'two_left_sidebar', 'two_right_sidebar'];
16
16
  export const THREE_COL_LAYOUTS = ['three_equal', 'three_with_sidebars'];
@@ -664,26 +664,30 @@ export const distributeLayoutColumns = (editorAnalyticsAPI, inputMethod = INPUT_
664
664
  }
665
665
  const {
666
666
  layoutSectionNode,
667
- layoutSectionPos,
668
667
  selectedColumnIndices,
669
668
  selectedColumns
670
669
  } = selectedLayoutColumns;
671
670
  const endIndex = selectedColumnIndices[selectedColumnIndices.length - 1];
672
671
  const selectedColumnCount = selectedColumns.length;
673
672
  const totalColumnCount = layoutSectionNode.childCount;
674
-
675
- // Compute equal width for selected columns
676
673
  const existingWidths = mapChildren(layoutSectionNode, column => column.attrs.width);
677
- const selectedTotal = selectedColumnIndices.reduce((sum, idx) => sum + existingWidths[idx], 0);
678
- const equalWidth = selectedTotal / selectedColumnCount;
679
-
680
- // Early return if selected columns are already uniformly distributed — avoids spurious undo entry.
681
- if (selectedColumnIndices.every(idx => existingWidths[idx] === Number(equalWidth.toFixed(2)))) {
674
+ const selectedWidths = selectedColumns.map(({
675
+ node
676
+ }) => node.attrs.width);
677
+ const distribution = calculateDistribution(selectedWidths);
678
+ if (!distribution) {
679
+ return null;
680
+ }
681
+ const {
682
+ selectedTotal,
683
+ equalWidth
684
+ } = distribution;
685
+ if (isDistributedUniformly(selectedWidths, distribution)) {
682
686
  return null;
683
687
  }
684
688
 
685
689
  // Build new widths array: selected columns get equal share, unselected unchanged.
686
- // Assign truncated (2dp) equal widths to all selected cols except the last, which absorbs
690
+ // Assign rounded (2dp) equal widths to all selected cols except the last, which absorbs
687
691
  // the rounding remainder so the sum of selected widths equals selectedTotal exactly.
688
692
  const selectedIndexSet = new Set(selectedColumnIndices);
689
693
  let assignedToSelected = 0;
@@ -694,16 +698,24 @@ export const distributeLayoutColumns = (editorAnalyticsAPI, inputMethod = INPUT_
694
698
  }
695
699
  selectedAssignedCount += 1;
696
700
  if (selectedAssignedCount < selectedColumnCount) {
697
- const truncated = Number(equalWidth.toFixed(2));
698
- assignedToSelected += truncated;
699
- return truncated;
701
+ assignedToSelected += equalWidth;
702
+ return equalWidth;
700
703
  }
701
704
  // Last selected column: absorb the remainder to avoid drift
702
705
  return Number((selectedTotal - assignedToSelected).toFixed(2));
703
706
  });
704
707
 
705
- // Apply widths via replaceWith (mirroring forceColumnWidths approach)
706
- tr.replaceWith(layoutSectionPos + 1, layoutSectionPos + layoutSectionNode.nodeSize - 1, columnWidth(layoutSectionNode, tr.doc.type.schema, newWidths));
708
+ // Apply widths via setNodeMarkup per selected column — keeps nodes in place (preserves identity, marks, decorations)
709
+ selectedColumns.forEach(({
710
+ node,
711
+ pos
712
+ }, i) => {
713
+ const colIdx = selectedColumnIndices[i];
714
+ tr.setNodeMarkup(pos, node.type, {
715
+ ...node.attrs,
716
+ width: newWidths[colIdx]
717
+ });
718
+ });
707
719
  editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
708
720
  action: ACTION.UPDATED,
709
721
  actionSubject: ACTION_SUBJECT.DOCUMENT,
@@ -1,6 +1,5 @@
1
1
  const roundLayoutColumnWidth = width => Number(width.toFixed(2));
2
2
  const sumWidths = widths => widths.reduce((sum, width) => sum + width, 0);
3
- const isValidWidth = width => Number.isFinite(width) && width > 0;
4
3
  const normaliseWidthsTotal = (widths, totalWidth, minWidth) => {
5
4
  const roundedWidths = widths.map(roundLayoutColumnWidth);
6
5
  const remainder = roundLayoutColumnWidth(totalWidth - sumWidths(roundedWidths));
@@ -19,6 +18,7 @@ const normaliseWidthsTotal = (widths, totalWidth, minWidth) => {
19
18
  }
20
19
  return roundedWidths.map((width, index) => index === adjustmentIndex ? adjustedWidth : width);
21
20
  };
21
+ const isValidWidth = width => Number.isFinite(width) && width > 0;
22
22
  const redistributeWithMinimumWidth = ({
23
23
  minWidth,
24
24
  totalWidth,
@@ -61,6 +61,39 @@ const redistributeWithMinimumWidth = ({
61
61
  });
62
62
  return widths;
63
63
  };
64
+
65
+ /**
66
+ * Returns true when the given selected columns already reflect the distribution that
67
+ * `distributeLayoutColumns` would produce — i.e. the first N-1 cols each hold
68
+ * `equalWidth` (rounded to 2 dp) and the last col absorbs the rounding remainder.
69
+ *
70
+ * This mirrors the action's "last col absorbs remainder" logic so that the UI can
71
+ * disable the option when it would be a no-op, avoiding spurious undo entries.
72
+ */
73
+
74
+ export const calculateDistribution = selectedWidths => {
75
+ const count = selectedWidths.length;
76
+ if (count < 2) {
77
+ return undefined;
78
+ }
79
+ const selectedTotal = sumWidths(selectedWidths);
80
+ const equalWidth = roundLayoutColumnWidth(selectedTotal / count);
81
+ return {
82
+ selectedTotal,
83
+ equalWidth
84
+ };
85
+ };
86
+ export function isDistributedUniformly(selectedWidths, distribution = calculateDistribution(selectedWidths)) {
87
+ if (!distribution || selectedWidths.length < 2) {
88
+ return false;
89
+ }
90
+ const {
91
+ selectedTotal,
92
+ equalWidth
93
+ } = distribution;
94
+ const lastColWidth = roundLayoutColumnWidth(selectedTotal - equalWidth * (selectedWidths.length - 1));
95
+ return selectedWidths.slice(0, -1).every(width => width === equalWidth) && selectedWidths[selectedWidths.length - 1] === lastColWidth;
96
+ }
64
97
  export const redistributeAfterDeletion = (currentWidths, removeIndex, minWidth) => {
65
98
  if (currentWidths.length === 0 || removeIndex < 0 || removeIndex >= currentWidths.length || !isValidWidth(minWidth)) {
66
99
  return currentWidths;
@@ -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
  };
@@ -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;
@@ -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
  };
@@ -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[];
@@ -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[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-layout",
3
- "version": "10.8.0",
3
+ "version": "10.8.1",
4
4
  "description": "Layout plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -48,14 +48,14 @@
48
48
  "@atlaskit/icon": "^35.3.0",
49
49
  "@atlaskit/icon-lab": "^6.12.0",
50
50
  "@atlaskit/platform-feature-flags": "^1.1.0",
51
- "@atlaskit/tmp-editor-statsig": "^84.0.0",
52
- "@atlaskit/tokens": "^13.0.0",
51
+ "@atlaskit/tmp-editor-statsig": "^85.0.0",
52
+ "@atlaskit/tokens": "^13.1.0",
53
53
  "@babel/runtime": "^7.0.0",
54
54
  "@emotion/react": "^11.7.1",
55
55
  "bind-event-listener": "^3.0.0"
56
56
  },
57
57
  "peerDependencies": {
58
- "@atlaskit/editor-common": "^114.48.0",
58
+ "@atlaskit/editor-common": "^114.50.0",
59
59
  "react": "^18.2.0",
60
60
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
61
61
  },
@@ -1,2 +0,0 @@
1
- export declare const redistributeAfterDeletion: (currentWidths: number[], removeIndex: number, minWidth: number) => number[];
2
- export declare const redistributeProportionally: (currentWidths: number[], insertIndex: number, maxColumns: number, minWidth: number) => number[];
@@ -1,2 +0,0 @@
1
- export declare const redistributeAfterDeletion: (currentWidths: number[], removeIndex: number, minWidth: number) => number[];
2
- export declare const redistributeProportionally: (currentWidths: number[], insertIndex: number, maxColumns: number, minWidth: number) => number[];