@atlaskit/editor-plugin-layout 10.5.0 → 10.6.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 (52) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/cjs/layoutPlugin.js +4 -1
  3. package/dist/cjs/pm-plugins/actions.js +87 -64
  4. package/dist/cjs/pm-plugins/main.js +4 -2
  5. package/dist/cjs/pm-plugins/utils/layout-column-selection.js +128 -0
  6. package/dist/cjs/ui/LayoutColumnMenu/DeleteColumnDropdownItem.js +7 -4
  7. package/dist/cjs/ui/LayoutColumnMenu/InsertColumnDropdownItem.js +5 -6
  8. package/dist/cjs/ui/LayoutColumnMenu/VerticalAlignDropdownItem.js +9 -7
  9. package/dist/cjs/ui/LayoutColumnMenu/VerticalAlignNestedMenu.js +15 -6
  10. package/dist/cjs/ui/LayoutColumnMenu/index.js +17 -7
  11. package/dist/cjs/ui/LayoutColumnMenu/useSelectedLayoutColumns.js +14 -0
  12. package/dist/es2019/layoutPlugin.js +4 -1
  13. package/dist/es2019/pm-plugins/actions.js +73 -58
  14. package/dist/es2019/pm-plugins/main.js +4 -2
  15. package/dist/es2019/pm-plugins/utils/layout-column-selection.js +118 -0
  16. package/dist/es2019/ui/LayoutColumnMenu/DeleteColumnDropdownItem.js +7 -4
  17. package/dist/es2019/ui/LayoutColumnMenu/InsertColumnDropdownItem.js +5 -6
  18. package/dist/es2019/ui/LayoutColumnMenu/VerticalAlignDropdownItem.js +9 -6
  19. package/dist/es2019/ui/LayoutColumnMenu/VerticalAlignNestedMenu.js +15 -5
  20. package/dist/es2019/ui/LayoutColumnMenu/index.js +16 -6
  21. package/dist/es2019/ui/LayoutColumnMenu/useSelectedLayoutColumns.js +6 -0
  22. package/dist/esm/layoutPlugin.js +5 -2
  23. package/dist/esm/pm-plugins/actions.js +87 -64
  24. package/dist/esm/pm-plugins/main.js +4 -2
  25. package/dist/esm/pm-plugins/utils/layout-column-selection.js +122 -0
  26. package/dist/esm/ui/LayoutColumnMenu/DeleteColumnDropdownItem.js +7 -4
  27. package/dist/esm/ui/LayoutColumnMenu/InsertColumnDropdownItem.js +5 -6
  28. package/dist/esm/ui/LayoutColumnMenu/VerticalAlignDropdownItem.js +10 -8
  29. package/dist/esm/ui/LayoutColumnMenu/VerticalAlignNestedMenu.js +15 -6
  30. package/dist/esm/ui/LayoutColumnMenu/index.js +17 -7
  31. package/dist/esm/ui/LayoutColumnMenu/useSelectedLayoutColumns.js +8 -0
  32. package/dist/types/layoutPluginType.d.ts +1 -1
  33. package/dist/types/pm-plugins/actions.d.ts +3 -2
  34. package/dist/types/pm-plugins/types.d.ts +1 -0
  35. package/dist/types/pm-plugins/utils/layout-column-selection.d.ts +18 -0
  36. package/dist/types/ui/LayoutColumnMenu/useSelectedLayoutColumns.d.ts +4 -0
  37. package/dist/types-ts4.5/layoutPluginType.d.ts +1 -1
  38. package/dist/types-ts4.5/pm-plugins/actions.d.ts +3 -2
  39. package/dist/types-ts4.5/pm-plugins/types.d.ts +1 -0
  40. package/dist/types-ts4.5/pm-plugins/utils/layout-column-selection.d.ts +18 -0
  41. package/dist/types-ts4.5/ui/LayoutColumnMenu/useSelectedLayoutColumns.d.ts +4 -0
  42. package/package.json +6 -6
  43. package/dist/cjs/ui/LayoutColumnMenu/layoutColumnSelection.js +0 -21
  44. package/dist/cjs/ui/LayoutColumnMenu/useCurrentLayoutColumn.js +0 -20
  45. package/dist/es2019/ui/LayoutColumnMenu/layoutColumnSelection.js +0 -9
  46. package/dist/es2019/ui/LayoutColumnMenu/useCurrentLayoutColumn.js +0 -10
  47. package/dist/esm/ui/LayoutColumnMenu/layoutColumnSelection.js +0 -15
  48. package/dist/esm/ui/LayoutColumnMenu/useCurrentLayoutColumn.js +0 -14
  49. package/dist/types/ui/LayoutColumnMenu/layoutColumnSelection.d.ts +0 -7
  50. package/dist/types/ui/LayoutColumnMenu/useCurrentLayoutColumn.d.ts +0 -5
  51. package/dist/types-ts4.5/ui/LayoutColumnMenu/layoutColumnSelection.d.ts +0 -7
  52. package/dist/types-ts4.5/ui/LayoutColumnMenu/useCurrentLayoutColumn.d.ts +0 -5
@@ -12,6 +12,7 @@ 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 { getSelectedLayoutColumns } from './utils/layout-column-selection';
15
16
  import { redistributeAfterDeletion, redistributeProportionally } from './utils/redistribute-proportionally';
16
17
  export var ONE_COL_LAYOUTS = ['single'];
17
18
  export var TWO_COL_LAYOUTS = ['two_equal', 'two_left_sidebar', 'two_right_sidebar'];
@@ -547,44 +548,24 @@ var formatLayoutName = function formatLayoutName(layout) {
547
548
  export function getEffectiveMaxLayoutColumns() {
548
549
  return editorExperiment('advanced_layouts', true) ? MAX_LAYOUT_COLUMNS : MAX_STANDARD_LAYOUT_COLUMNS;
549
550
  }
550
- var getSelectedLayoutColumnInSection = function getSelectedLayoutColumnInSection(_ref4) {
551
- var doc = _ref4.doc,
552
- selection = _ref4.selection;
553
- var _doc$type$schema$node = doc.type.schema.nodes,
554
- layoutColumn = _doc$type$schema$node.layoutColumn,
555
- layoutSection = _doc$type$schema$node.layoutSection;
556
- if (!(selection instanceof NodeSelection) || selection.node.type !== layoutColumn) {
557
- return;
558
- }
559
- var $from = selection.$from;
560
- if ($from.parent.type !== layoutSection) {
561
- return;
562
- }
563
- var selectedColumnIndex = $from.index($from.depth);
564
- var selectedColumnNode = selection.node;
565
- var selectedColumnPos = selection.from;
566
- var layoutSectionPos = $from.before($from.depth);
567
- return {
568
- layoutSectionNode: $from.parent,
569
- layoutSectionPos: layoutSectionPos,
570
- selectedColumnIndex: selectedColumnIndex,
571
- selectedColumnNode: selectedColumnNode,
572
- selectedColumnPos: selectedColumnPos
573
- };
574
- };
575
551
  var insertLayoutColumnAt = function insertLayoutColumnAt(side, editorAnalyticsAPI) {
576
- return function (_ref5) {
577
- var tr = _ref5.tr;
552
+ return function (_ref4) {
553
+ var tr = _ref4.tr;
578
554
  if (!expValEqualsNoExposure('platform_editor_layout_column_menu', 'isEnabled', true)) {
579
555
  return null;
580
556
  }
581
- var selectedColumn = getSelectedLayoutColumnInSection(tr);
582
- if (!selectedColumn) {
557
+ var selectedLayoutColumns = getSelectedLayoutColumns(tr.selection);
558
+ if (!selectedLayoutColumns) {
583
559
  return null;
584
560
  }
585
- var layoutSectionNode = selectedColumn.layoutSectionNode,
586
- layoutSectionPos = selectedColumn.layoutSectionPos,
587
- selectedColumnIndex = selectedColumn.selectedColumnIndex;
561
+ var layoutSectionNode = selectedLayoutColumns.layoutSectionNode,
562
+ layoutSectionPos = selectedLayoutColumns.layoutSectionPos,
563
+ selectedColumnIndices = selectedLayoutColumns.selectedColumnIndices,
564
+ selectedColumns = selectedLayoutColumns.selectedColumns;
565
+ var startIndex = selectedColumnIndices[0];
566
+ var endIndex = selectedColumnIndices[selectedColumnIndices.length - 1];
567
+ var selectedColumnIndex = side === 'left' ? startIndex : endIndex;
568
+ var selectedColumnCount = selectedColumns.length;
588
569
  if (layoutSectionNode.childCount >= getEffectiveMaxLayoutColumns()) {
589
570
  return null;
590
571
  }
@@ -621,9 +602,11 @@ var insertLayoutColumnAt = function insertLayoutColumnAt(side, editorAnalyticsAP
621
602
  actionSubjectId: ACTION_SUBJECT_ID.LAYOUT_COLUMN,
622
603
  attributes: {
623
604
  columnCount: redistributedWidths.length,
605
+ endIndex: endIndex,
624
606
  inputMethod: INPUT_METHOD.LAYOUT_COLUMN_MENU,
625
- selectedIndex: selectedColumnIndex,
626
- side: side
607
+ selectedCount: selectedColumnCount,
608
+ side: side,
609
+ startIndex: startIndex
627
610
  },
628
611
  eventType: EVENT_TYPE.TRACK
629
612
  })(tr);
@@ -634,35 +617,61 @@ var insertLayoutColumnAt = function insertLayoutColumnAt(side, editorAnalyticsAP
634
617
  export var insertLayoutColumn = function insertLayoutColumn(side, editorAnalyticsAPI) {
635
618
  return insertLayoutColumnAt(side, editorAnalyticsAPI);
636
619
  };
637
- export var setLayoutColumnValign = function setLayoutColumnValign(valign) {
638
- return function (_ref6) {
639
- var tr = _ref6.tr;
620
+ export var setLayoutColumnValign = function setLayoutColumnValign(valign, editorAnalyticsAPI) {
621
+ return function (_ref5) {
622
+ var tr = _ref5.tr;
640
623
  if (!expValEqualsNoExposure('platform_editor_layout_column_menu', 'isEnabled', true)) {
641
624
  return null;
642
625
  }
643
- var layoutColumn = tr.doc.type.schema.nodes.layoutColumn;
644
- var selectedColumn = tr.selection instanceof NodeSelection && tr.selection.node.type === layoutColumn ? {
645
- node: tr.selection.node,
646
- pos: tr.selection.from
647
- } : undefined;
648
- if (!selectedColumn) {
626
+ var selectedLayoutColumns = getSelectedLayoutColumns(tr.selection);
627
+ if (!selectedLayoutColumns) {
649
628
  return null;
650
629
  }
651
- if (selectedColumn.node.attrs.valign === valign) {
630
+ var selectedColumnIndices = selectedLayoutColumns.selectedColumnIndices,
631
+ selectedColumns = selectedLayoutColumns.selectedColumns;
632
+ var columnsToUpdate = selectedColumns.filter(function (_ref6) {
633
+ var node = _ref6.node;
634
+ return node.attrs.valign !== valign;
635
+ });
636
+ if (columnsToUpdate.length === 0) {
652
637
  return null;
653
638
  }
654
- tr.setNodeMarkup(selectedColumn.pos, selectedColumn.node.type, _objectSpread(_objectSpread({}, selectedColumn.node.attrs), {}, {
655
- valign: valign
656
- }));
639
+ var startIndex = selectedColumnIndices[0];
640
+ var endIndex = selectedColumnIndices[selectedColumnIndices.length - 1];
641
+ var selectedColumnCount = selectedColumns.length;
642
+ var updatedColumnCount = columnsToUpdate.length;
643
+ columnsToUpdate.forEach(function (_ref7) {
644
+ var node = _ref7.node,
645
+ pos = _ref7.pos;
646
+ tr.setNodeMarkup(pos, node.type, _objectSpread(_objectSpread({}, node.attrs), {}, {
647
+ valign: valign
648
+ }));
649
+ });
650
+ editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
651
+ action: ACTION.UPDATED,
652
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
653
+ actionSubjectId: ACTION_SUBJECT_ID.LAYOUT_COLUMN,
654
+ attributes: {
655
+ endIndex: endIndex,
656
+ inputMethod: INPUT_METHOD.LAYOUT_COLUMN_MENU,
657
+ selectedCount: selectedColumnCount,
658
+ startIndex: startIndex,
659
+ updatedCount: updatedColumnCount,
660
+ valign: valign
661
+ },
662
+ eventType: EVENT_TYPE.TRACK
663
+ })(tr);
657
664
  tr.setMeta('scrollIntoView', false);
658
665
  return tr;
659
666
  };
660
667
  };
661
- export var toggleLayoutColumnMenu = function toggleLayoutColumnMenu(_ref7) {
662
- var isOpen = _ref7.isOpen;
663
- return function (_ref8) {
664
- var tr = _ref8.tr;
668
+ export var toggleLayoutColumnMenu = function toggleLayoutColumnMenu(_ref8) {
669
+ var anchorPos = _ref8.anchorPos,
670
+ isOpen = _ref8.isOpen;
671
+ return function (_ref9) {
672
+ var tr = _ref9.tr;
665
673
  tr.setMeta('toggleLayoutColumnMenu', {
674
+ anchorPos: anchorPos,
666
675
  isOpen: isOpen
667
676
  });
668
677
  tr.setMeta('scrollIntoView', false);
@@ -670,18 +679,23 @@ export var toggleLayoutColumnMenu = function toggleLayoutColumnMenu(_ref7) {
670
679
  };
671
680
  };
672
681
  export var deleteLayoutColumn = function deleteLayoutColumn(editorAnalyticsAPI) {
673
- return function (_ref9) {
674
- var tr = _ref9.tr;
682
+ return function (_ref0) {
683
+ var tr = _ref0.tr;
675
684
  if (!expValEqualsNoExposure('platform_editor_layout_column_menu', 'isEnabled', true)) {
676
685
  return null;
677
686
  }
678
- var selectedColumn = getSelectedLayoutColumnInSection(tr);
679
- if (!selectedColumn) {
687
+ var selectedLayoutColumns = getSelectedLayoutColumns(tr.selection);
688
+ if (!selectedLayoutColumns) {
680
689
  return null;
681
690
  }
682
- var layoutSectionNode = selectedColumn.layoutSectionNode,
683
- layoutSectionPos = selectedColumn.layoutSectionPos,
684
- selectedColumnIndex = selectedColumn.selectedColumnIndex;
691
+ var layoutSectionNode = selectedLayoutColumns.layoutSectionNode,
692
+ layoutSectionPos = selectedLayoutColumns.layoutSectionPos,
693
+ selectedColumnIndices = selectedLayoutColumns.selectedColumnIndices,
694
+ selectedColumns = selectedLayoutColumns.selectedColumns;
695
+ var startIndex = selectedColumnIndices[0];
696
+ var endIndex = selectedColumnIndices[selectedColumnIndices.length - 1];
697
+ var selectedColumnCount = selectedColumns.length;
698
+ var selectedColumnIndexSet = new Set(selectedColumnIndices);
685
699
  var emitDeleteColumnAnalytics = function emitDeleteColumnAnalytics(columnCount) {
686
700
  editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
687
701
  action: ACTION.DELETED,
@@ -689,25 +703,27 @@ export var deleteLayoutColumn = function deleteLayoutColumn(editorAnalyticsAPI)
689
703
  actionSubjectId: ACTION_SUBJECT_ID.LAYOUT_COLUMN,
690
704
  attributes: {
691
705
  columnCount: columnCount,
706
+ endIndex: endIndex,
692
707
  inputMethod: INPUT_METHOD.LAYOUT_COLUMN_MENU,
693
- selectedIndex: selectedColumnIndex
708
+ selectedCount: selectedColumnCount,
709
+ startIndex: startIndex
694
710
  },
695
711
  eventType: EVENT_TYPE.TRACK
696
712
  })(tr);
697
713
  };
698
714
 
699
- // If only one column remains, remove the entire layoutSection
700
- if (layoutSectionNode.childCount === 1) {
715
+ // If all columns are selected, remove the entire layoutSection
716
+ if (selectedColumnCount === layoutSectionNode.childCount) {
701
717
  tr.delete(layoutSectionPos, layoutSectionPos + layoutSectionNode.nodeSize);
702
718
  emitDeleteColumnAnalytics(0);
703
719
  tr.setMeta('scrollIntoView', false);
704
720
  return tr;
705
721
  }
706
722
 
707
- // Build new column list without the selected column
723
+ // Build new column list without the selected columns
708
724
  var remainingColumns = [];
709
725
  layoutSectionNode.forEach(function (column, _offset, index) {
710
- if (index !== selectedColumnIndex) {
726
+ if (!selectedColumnIndexSet.has(index)) {
711
727
  remainingColumns.push(column);
712
728
  }
713
729
  });
@@ -716,7 +732,14 @@ export var deleteLayoutColumn = function deleteLayoutColumn(editorAnalyticsAPI)
716
732
  var existingWidths = mapChildren(layoutSectionNode, function (column) {
717
733
  return column.attrs.width;
718
734
  });
719
- var redistributed = redistributeAfterDeletion(existingWidths, selectedColumnIndex, MIN_LAYOUT_COLUMN_WIDTH_PERCENT);
735
+ var redistributed = selectedColumnIndices.slice()
736
+ // Delete highest indices first so lower original indices still point at the same columns
737
+ // as each redistribution step shrinks the widths array.
738
+ .sort(function (a, b) {
739
+ return b - a;
740
+ }).reduce(function (widths, selectedIndex) {
741
+ return redistributeAfterDeletion(widths, selectedIndex, MIN_LAYOUT_COLUMN_WIDTH_PERCENT);
742
+ }, existingWidths);
720
743
  var updatedLayoutSectionNode = layoutSectionNode.copy(Fragment.fromArray(remainingColumns));
721
744
  tr.replaceWith(layoutSectionPos + 1, layoutSectionPos + layoutSectionNode.nodeSize - 1, columnWidth(updatedLayoutSectionNode, tr.doc.type.schema, redistributed));
722
745
  emitDeleteColumnAnalytics(redistributed.length);
@@ -66,7 +66,8 @@ var getInitialPluginState = function getInitialPluginState(options, state) {
66
66
  selectedLayout: selectedLayout,
67
67
  allowSingleColumnLayout: allowSingleColumnLayout,
68
68
  isResizing: false,
69
- isLayoutColumnMenuOpen: false
69
+ isLayoutColumnMenuOpen: false,
70
+ layoutColumnMenuAnchorPos: undefined
70
71
  };
71
72
  };
72
73
 
@@ -118,7 +119,8 @@ export default (function (options) {
118
119
  var _columnMenuMeta$isOpe, _tr$getMeta, _pluginKey$getState;
119
120
  var columnMenuMeta = tr.getMeta('toggleLayoutColumnMenu');
120
121
  var nextPluginState = columnMenuMeta ? _objectSpread(_objectSpread({}, pluginState), {}, {
121
- isLayoutColumnMenuOpen: (_columnMenuMeta$isOpe = columnMenuMeta.isOpen) !== null && _columnMenuMeta$isOpe !== void 0 ? _columnMenuMeta$isOpe : !pluginState.isLayoutColumnMenuOpen
122
+ isLayoutColumnMenuOpen: (_columnMenuMeta$isOpe = columnMenuMeta.isOpen) !== null && _columnMenuMeta$isOpe !== void 0 ? _columnMenuMeta$isOpe : !pluginState.isLayoutColumnMenuOpen,
123
+ layoutColumnMenuAnchorPos: columnMenuMeta.isOpen === false ? undefined : columnMenuMeta.anchorPos
122
124
  }) : pluginState;
123
125
  var isResizing = editorExperiment('single_column_layouts', true) ? (_tr$getMeta = tr.getMeta('is-resizer-resizing')) !== null && _tr$getMeta !== void 0 ? _tr$getMeta : (_pluginKey$getState = pluginKey.getState(oldState)) === null || _pluginKey$getState === void 0 ? void 0 : _pluginKey$getState.isResizing : false;
124
126
  if (tr.docChanged || tr.selectionSet) {
@@ -0,0 +1,122 @@
1
+ import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
2
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
3
+ var isLayoutColumn = function isLayoutColumn(node) {
4
+ return (node === null || node === void 0 ? void 0 : node.type.name) === 'layoutColumn';
5
+ };
6
+ var isLayoutSection = function isLayoutSection(node) {
7
+ return (node === null || node === void 0 ? void 0 : node.type.name) === 'layoutSection';
8
+ };
9
+ var getLayoutColumnIndexAtPos = function getLayoutColumnIndexAtPos($pos) {
10
+ for (var depth = $pos.depth; depth > 0; depth--) {
11
+ if (isLayoutColumn($pos.node(depth)) && isLayoutSection($pos.node(depth - 1))) {
12
+ return $pos.index(depth - 1);
13
+ }
14
+ }
15
+ return undefined;
16
+ };
17
+ var getLayoutSectionDepth = function getLayoutSectionDepth(selection) {
18
+ var $from = selection.$from,
19
+ $to = selection.$to;
20
+ var sharedDepth = $from.sharedDepth($to.pos);
21
+ for (var depth = sharedDepth; depth > 0; depth--) {
22
+ if (isLayoutSection($from.node(depth))) {
23
+ return depth;
24
+ }
25
+ }
26
+ return undefined;
27
+ };
28
+ export var getSelectedLayoutColumns = function getSelectedLayoutColumns(selection) {
29
+ if (!selection) {
30
+ return undefined;
31
+ }
32
+ if (selection instanceof NodeSelection && isLayoutColumn(selection.node)) {
33
+ var _$from = selection.$from;
34
+ var _layoutSectionNode = _$from.parent;
35
+ if (!isLayoutSection(_layoutSectionNode)) {
36
+ return undefined;
37
+ }
38
+ var selectedColumnIndex = _$from.index(_$from.depth);
39
+ return {
40
+ layoutSectionNode: _layoutSectionNode,
41
+ layoutSectionPos: _$from.before(_$from.depth),
42
+ selectedColumnIndices: [selectedColumnIndex],
43
+ selectedColumns: [{
44
+ index: selectedColumnIndex,
45
+ node: selection.node,
46
+ pos: selection.from
47
+ }]
48
+ };
49
+ }
50
+ if (selection.empty) {
51
+ return undefined;
52
+ }
53
+ if (!editorExperiment('platform_editor_block_menu', true)) {
54
+ return undefined;
55
+ }
56
+ var layoutSectionDepth = getLayoutSectionDepth(selection);
57
+ if (layoutSectionDepth === undefined) {
58
+ return undefined;
59
+ }
60
+ var $from = selection.$from,
61
+ $to = selection.$to;
62
+ var layoutSectionNode = $from.node(layoutSectionDepth);
63
+ var layoutSectionPos = $from.before(layoutSectionDepth);
64
+ var selectedColumns = [];
65
+ var invalidSelection = false;
66
+ layoutSectionNode.forEach(function (column, offset, index) {
67
+ var columnStart = layoutSectionPos + 1 + offset;
68
+ var columnEnd = columnStart + column.nodeSize;
69
+ var intersectsColumn = selection.from < columnEnd && selection.to > columnStart;
70
+ if (!intersectsColumn) {
71
+ return;
72
+ }
73
+ if (!isLayoutColumn(column)) {
74
+ invalidSelection = true;
75
+ return;
76
+ }
77
+ selectedColumns.push({
78
+ index: index,
79
+ node: column,
80
+ pos: columnStart
81
+ });
82
+ });
83
+
84
+ // TextSelection inside a single column is normal text editing, not a selected column set.
85
+ if (invalidSelection || selectedColumns.length < 2) {
86
+ return undefined;
87
+ }
88
+ var firstColumn = selectedColumns[0];
89
+ var lastColumn = selectedColumns[selectedColumns.length - 1];
90
+ var startColumnIndex = getLayoutColumnIndexAtPos($from);
91
+ var endColumnIndex = getLayoutColumnIndexAtPos($to);
92
+ if (startColumnIndex !== undefined && startColumnIndex !== firstColumn.index || endColumnIndex !== undefined && endColumnIndex !== lastColumn.index) {
93
+ return undefined;
94
+ }
95
+ return {
96
+ layoutSectionNode: layoutSectionNode,
97
+ layoutSectionPos: layoutSectionPos,
98
+ selectedColumnIndices: selectedColumns.map(function (_ref) {
99
+ var index = _ref.index;
100
+ return index;
101
+ }),
102
+ selectedColumns: selectedColumns
103
+ };
104
+ };
105
+ export var getLayoutSectionColumnCount = function getLayoutSectionColumnCount(layoutSection) {
106
+ return (layoutSection === null || layoutSection === void 0 ? void 0 : layoutSection.type.name) === 'layoutSection' ? layoutSection.childCount : 0;
107
+ };
108
+ export var getLayoutColumnValign = function getLayoutColumnValign(layoutColumn) {
109
+ return layoutColumn === null || layoutColumn === void 0 ? void 0 : layoutColumn.attrs.valign;
110
+ };
111
+ export var getLayoutColumnMenuAnchorPos = function getLayoutColumnMenuAnchorPos(selection, anchorPosFromHandle) {
112
+ var _clickedSelectedColum, _selectedLayoutColumn;
113
+ var selectedLayoutColumns = getSelectedLayoutColumns(selection);
114
+ if (!selectedLayoutColumns) {
115
+ return undefined;
116
+ }
117
+ var clickedSelectedColumn = selectedLayoutColumns.selectedColumns.find(function (_ref2) {
118
+ var pos = _ref2.pos;
119
+ return pos === anchorPosFromHandle;
120
+ });
121
+ 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;
122
+ };
@@ -2,12 +2,12 @@ import React, { useCallback } 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 { useCurrentLayoutColumn } from './useCurrentLayoutColumn';
5
+ import { useSelectedLayoutColumns } from './useSelectedLayoutColumns';
6
6
  var DeleteColumnDropdownItem = function DeleteColumnDropdownItem(_ref) {
7
7
  var api = _ref.api;
8
8
  var _useIntl = useIntl(),
9
9
  formatMessage = _useIntl.formatMessage;
10
- var currentColumn = useCurrentLayoutColumn(api);
10
+ var selectedLayoutColumns = useSelectedLayoutColumns(api);
11
11
  var onClick = useCallback(function () {
12
12
  var _api$layout, _api$core;
13
13
  var deleteCommand = api === null || api === void 0 || (_api$layout = api.layout) === null || _api$layout === void 0 ? void 0 : _api$layout.commands.deleteLayoutColumn;
@@ -25,11 +25,14 @@ var DeleteColumnDropdownItem = function DeleteColumnDropdownItem(_ref) {
25
25
  return tr;
26
26
  });
27
27
  }, [api]);
28
- if (currentColumn === undefined) {
28
+ if (selectedLayoutColumns === undefined) {
29
29
  return null;
30
30
  }
31
+ var selectedColumnCount = selectedLayoutColumns.selectedColumns.length;
31
32
  return /*#__PURE__*/React.createElement(ToolbarDropdownItem, {
32
33
  onClick: onClick
33
- }, formatMessage(layoutMessages.deleteColumn));
34
+ }, formatMessage(layoutMessages.deleteColumn, {
35
+ count: selectedColumnCount
36
+ }));
34
37
  };
35
38
  export { DeleteColumnDropdownItem };
@@ -3,8 +3,8 @@ import { useIntl } from 'react-intl';
3
3
  import { layoutMessages } from '@atlaskit/editor-common/messages';
4
4
  import { TableColumnAddLeftIcon, TableColumnAddRightIcon, ToolbarDropdownItem } from '@atlaskit/editor-toolbar';
5
5
  import { getEffectiveMaxLayoutColumns } from '../../pm-plugins/actions';
6
- import { getLayoutSectionColumnCount } from './layoutColumnSelection';
7
- import { useCurrentLayoutColumn, useCurrentLayoutSection } from './useCurrentLayoutColumn';
6
+ import { getLayoutSectionColumnCount } from '../../pm-plugins/utils/layout-column-selection';
7
+ import { useSelectedLayoutColumns } from './useSelectedLayoutColumns';
8
8
  var INSERT_COLUMN_OPTIONS = {
9
9
  left: {
10
10
  Icon: TableColumnAddLeftIcon,
@@ -25,11 +25,10 @@ export var InsertColumnDropdownItem = function InsertColumnDropdownItem(_ref) {
25
25
  var _INSERT_COLUMN_OPTION = INSERT_COLUMN_OPTIONS[side],
26
26
  Icon = _INSERT_COLUMN_OPTION.Icon,
27
27
  label = _INSERT_COLUMN_OPTION.label;
28
- var currentColumn = useCurrentLayoutColumn(api);
29
- var currentLayoutSection = useCurrentLayoutSection(api);
30
- var columnCount = getLayoutSectionColumnCount(currentLayoutSection);
28
+ var selectedLayoutColumns = useSelectedLayoutColumns(api);
29
+ var columnCount = getLayoutSectionColumnCount(selectedLayoutColumns === null || selectedLayoutColumns === void 0 ? void 0 : selectedLayoutColumns.layoutSectionNode);
31
30
  var maxColumnCount = getEffectiveMaxLayoutColumns();
32
- var canInsertColumn = currentColumn !== undefined && columnCount < maxColumnCount;
31
+ var canInsertColumn = selectedLayoutColumns !== undefined && columnCount < maxColumnCount;
33
32
  var onClick = useCallback(function () {
34
33
  var _api$layout, _api$core;
35
34
  var insertCommand = api === null || api === void 0 || (_api$layout = api.layout) === null || _api$layout === void 0 ? void 0 : _api$layout.commands.insertLayoutColumn(side);
@@ -1,19 +1,21 @@
1
- import React, { useCallback, useMemo } from 'react';
1
+ import React, { useCallback } from 'react';
2
2
  import { useIntl } from 'react-intl';
3
3
  import { ToolbarDropdownItem } from '@atlaskit/editor-toolbar';
4
- import { getLayoutColumnValign } from './layoutColumnSelection';
5
- import { useCurrentLayoutColumn } from './useCurrentLayoutColumn';
4
+ import { getLayoutColumnValign } from '../../pm-plugins/utils/layout-column-selection';
5
+ import { useSelectedLayoutColumns } from './useSelectedLayoutColumns';
6
6
  import { VERTICAL_ALIGN_ICONS } from './verticalAlignIcons';
7
7
  export var VerticalAlignDropdownItem = function VerticalAlignDropdownItem(_ref) {
8
+ var _selectedLayoutColumn;
8
9
  var api = _ref.api,
9
10
  label = _ref.label,
10
11
  value = _ref.value;
11
12
  var _useIntl = useIntl(),
12
13
  formatMessage = _useIntl.formatMessage;
13
- var currentColumn = useCurrentLayoutColumn(api);
14
- var currentValign = useMemo(function () {
15
- return getLayoutColumnValign(currentColumn);
16
- }, [currentColumn]);
14
+ var selectedLayoutColumns = useSelectedLayoutColumns(api);
15
+ var isSelected = (_selectedLayoutColumn = selectedLayoutColumns === null || selectedLayoutColumns === void 0 ? void 0 : selectedLayoutColumns.selectedColumns.every(function (_ref2) {
16
+ var node = _ref2.node;
17
+ return getLayoutColumnValign(node) === value;
18
+ })) !== null && _selectedLayoutColumn !== void 0 ? _selectedLayoutColumn : false;
17
19
  var Icon = VERTICAL_ALIGN_ICONS[value];
18
20
  var onClick = useCallback(function () {
19
21
  var _api$core, _api$layout;
@@ -24,7 +26,7 @@ export var VerticalAlignDropdownItem = function VerticalAlignDropdownItem(_ref)
24
26
  label: "",
25
27
  size: "small"
26
28
  }),
27
- isSelected: currentValign === value,
29
+ isSelected: isSelected,
28
30
  onClick: onClick
29
31
  }, formatMessage(label));
30
32
  };
@@ -2,20 +2,29 @@ import React, { useMemo } from 'react';
2
2
  import { useIntl } from 'react-intl';
3
3
  import { layoutMessages } from '@atlaskit/editor-common/messages';
4
4
  import { LayoutIcon, NestedDropdownRightIcon, ToolbarNestedDropdownMenu } from '@atlaskit/editor-toolbar';
5
- import { getLayoutColumnValign } from './layoutColumnSelection';
6
- import { useCurrentLayoutColumn } from './useCurrentLayoutColumn';
5
+ import { getLayoutColumnValign } from '../../pm-plugins/utils/layout-column-selection';
6
+ import { useSelectedLayoutColumns } from './useSelectedLayoutColumns';
7
7
  import { VERTICAL_ALIGN_ICONS } from './verticalAlignIcons';
8
8
  export var VerticalAlignNestedMenu = function VerticalAlignNestedMenu(_ref) {
9
9
  var api = _ref.api,
10
10
  children = _ref.children;
11
11
  var _useIntl = useIntl(),
12
12
  formatMessage = _useIntl.formatMessage;
13
- var currentColumn = useCurrentLayoutColumn(api);
13
+ var selectedLayoutColumns = useSelectedLayoutColumns(api);
14
14
  var currentValign = useMemo(function () {
15
- return getLayoutColumnValign(currentColumn);
16
- }, [currentColumn]);
15
+ var selectedColumns = selectedLayoutColumns === null || selectedLayoutColumns === void 0 ? void 0 : selectedLayoutColumns.selectedColumns;
16
+ var firstColumn = selectedColumns === null || selectedColumns === void 0 ? void 0 : selectedColumns[0];
17
+ var firstValign = getLayoutColumnValign(firstColumn === null || firstColumn === void 0 ? void 0 : firstColumn.node);
18
+ if (!firstValign || !(selectedColumns !== null && selectedColumns !== void 0 && selectedColumns.every(function (_ref2) {
19
+ var node = _ref2.node;
20
+ return getLayoutColumnValign(node) === firstValign;
21
+ }))) {
22
+ return undefined;
23
+ }
24
+ return firstValign;
25
+ }, [selectedLayoutColumns]);
17
26
  var TriggerIcon = currentValign ? VERTICAL_ALIGN_ICONS[currentValign] : LayoutIcon;
18
- if (!currentColumn) {
27
+ if (!selectedLayoutColumns) {
19
28
  return null;
20
29
  }
21
30
  return /*#__PURE__*/React.createElement(ToolbarNestedDropdownMenu, {
@@ -8,9 +8,9 @@ import { OutsideClickTargetRefContext, withReactEditorViewOuterListeners } from
8
8
  import { akEditorFloatingOverlapPanelZIndex } from '@atlaskit/editor-shared-styles';
9
9
  import { ToolbarDropdownMenuProvider } from '@atlaskit/editor-toolbar';
10
10
  import { SurfaceRenderer } from '@atlaskit/editor-ui-control-model';
11
+ import { getLayoutColumnMenuAnchorPos } from '../../pm-plugins/utils/layout-column-selection';
11
12
  import { LAYOUT_COLUMN_MENU_FALLBACKS } from './components';
12
13
  import { LAYOUT_COLUMN_MENU } from './keys';
13
- import { getLayoutColumnAtSelection } from './layoutColumnSelection';
14
14
  var PopupWithListeners = withReactEditorViewOuterListeners(Popup);
15
15
  var FALLBACK_MENU_HEIGHT = 300;
16
16
  var LAYOUT_COLUMN_MENU_POPUP_OFFSET = [0, 4];
@@ -18,12 +18,13 @@ var LAYOUT_COLUMN_MENU_POPUP_OFFSET = [0, 4];
18
18
  /**
19
19
  * Returns the drag handle button for the selected layout column.
20
20
  */
21
- var getLayoutColumnMenuTarget = function getLayoutColumnMenuTarget(editorView, selection) {
21
+ var getLayoutColumnMenuTarget = function getLayoutColumnMenuTarget(editorView, selection, anchorPosFromHandle) {
22
22
  var _columnDomRef$parentE;
23
- if (!getLayoutColumnAtSelection(selection) || (selection === null || selection === void 0 ? void 0 : selection.from) === undefined) {
23
+ var anchorPos = getLayoutColumnMenuAnchorPos(selection, anchorPosFromHandle);
24
+ if (anchorPos === undefined) {
24
25
  return null;
25
26
  }
26
- var columnDomRef = editorView.nodeDOM(selection.from);
27
+ var columnDomRef = editorView.nodeDOM(anchorPos);
27
28
  if (!(columnDomRef instanceof HTMLElement)) {
28
29
  return null;
29
30
  }
@@ -38,13 +39,15 @@ export var LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMenu(
38
39
  boundariesElement = _ref.boundariesElement,
39
40
  scrollableElement = _ref.scrollableElement;
40
41
  var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['layout', 'selection'], function (states) {
41
- var _states$layoutState$i, _states$layoutState, _states$selectionStat;
42
+ var _states$layoutState$i, _states$layoutState, _states$layoutState2, _states$selectionStat;
42
43
  return {
43
44
  isLayoutColumnMenuOpen: (_states$layoutState$i = (_states$layoutState = states.layoutState) === null || _states$layoutState === void 0 ? void 0 : _states$layoutState.isLayoutColumnMenuOpen) !== null && _states$layoutState$i !== void 0 ? _states$layoutState$i : false,
45
+ layoutColumnMenuAnchorPos: (_states$layoutState2 = states.layoutState) === null || _states$layoutState2 === void 0 ? void 0 : _states$layoutState2.layoutColumnMenuAnchorPos,
44
46
  selection: (_states$selectionStat = states.selectionState) === null || _states$selectionStat === void 0 ? void 0 : _states$selectionStat.selection
45
47
  };
46
48
  }),
47
49
  isLayoutColumnMenuOpen = _useSharedPluginState.isLayoutColumnMenuOpen,
50
+ layoutColumnMenuAnchorPos = _useSharedPluginState.layoutColumnMenuAnchorPos,
48
51
  selection = _useSharedPluginState.selection;
49
52
  var setOutsideClickTargetRef = useContext(OutsideClickTargetRefContext);
50
53
  var menuRef = useRef(null);
@@ -74,6 +77,13 @@ export var LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMenu(
74
77
  if (event.target instanceof Element && event.target.closest('[data-toolbar-nested-dropdown-menu]')) {
75
78
  return;
76
79
  }
80
+
81
+ // Clicking a drag handle should let the drag handle's own click handler
82
+ // update selection/menu state. Treating it as a generic outside click
83
+ // races that transaction and can immediately close the layout column menu.
84
+ if (event.target instanceof Element && event.target.closest(DRAG_HANDLE_SELECTOR)) {
85
+ return;
86
+ }
77
87
  closeLayoutColumnMenu();
78
88
  }, [closeLayoutColumnMenu]);
79
89
  var handleSetIsOpen = useCallback(function (isOpen) {
@@ -90,8 +100,8 @@ export var LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMenu(
90
100
  }, [setOutsideClickTargetRef]);
91
101
  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 : [];
92
102
  var target = useMemo(function () {
93
- return isLayoutColumnMenuOpen ? getLayoutColumnMenuTarget(editorView, selection) : null;
94
- }, [editorView, isLayoutColumnMenuOpen, selection]);
103
+ return isLayoutColumnMenuOpen ? getLayoutColumnMenuTarget(editorView, selection, layoutColumnMenuAnchorPos) : null;
104
+ }, [editorView, isLayoutColumnMenuOpen, layoutColumnMenuAnchorPos, selection]);
95
105
  var hasValidTarget = target instanceof HTMLElement;
96
106
  useEffect(function () {
97
107
  if (isLayoutColumnMenuOpen && (!hasValidTarget || components.length === 0)) {
@@ -0,0 +1,8 @@
1
+ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
2
+ import { getSelectedLayoutColumns } from '../../pm-plugins/utils/layout-column-selection';
3
+ export var useSelectedLayoutColumns = function useSelectedLayoutColumns(api) {
4
+ return useSharedPluginStateWithSelector(api, ['selection'], function (states) {
5
+ var _states$selectionStat;
6
+ return getSelectedLayoutColumns((_states$selectionStat = states.selectionState) === null || _states$selectionStat === void 0 ? void 0 : _states$selectionStat.selection);
7
+ });
8
+ };
@@ -31,7 +31,7 @@ export type LayoutPlugin = NextEditorPlugin<'layout', {
31
31
  commands: {
32
32
  deleteLayoutColumn: ReturnType<typeof deleteLayoutColumn>;
33
33
  insertLayoutColumn: (side: InsertLayoutColumnSide) => EditorCommand;
34
- setLayoutColumnValign: typeof setLayoutColumnValign;
34
+ setLayoutColumnValign: (valign: Parameters<typeof setLayoutColumnValign>[0]) => EditorCommand;
35
35
  toggleLayoutColumnMenu: typeof toggleLayoutColumnMenu;
36
36
  };
37
37
  dependencies: LayoutPluginDependencies;
@@ -33,8 +33,9 @@ export type InsertLayoutColumnSide = 'left' | 'right';
33
33
  */
34
34
  export declare function getEffectiveMaxLayoutColumns(): number;
35
35
  export declare const insertLayoutColumn: (side: InsertLayoutColumnSide, editorAnalyticsAPI?: EditorAnalyticsAPI) => EditorCommand;
36
- export declare const setLayoutColumnValign: (valign: Valign) => EditorCommand;
37
- export declare const toggleLayoutColumnMenu: ({ isOpen }: {
36
+ export declare const setLayoutColumnValign: (valign: Valign, editorAnalyticsAPI?: EditorAnalyticsAPI) => EditorCommand;
37
+ export declare const toggleLayoutColumnMenu: ({ anchorPos, isOpen }: {
38
+ anchorPos?: number;
38
39
  isOpen?: boolean;
39
40
  }) => EditorCommand;
40
41
  export declare const deleteLayoutColumn: (editorAnalyticsAPI?: EditorAnalyticsAPI) => EditorCommand;
@@ -6,6 +6,7 @@ export type LayoutState = {
6
6
  allowSingleColumnLayout: boolean;
7
7
  isLayoutColumnMenuOpen: boolean;
8
8
  isResizing: boolean;
9
+ layoutColumnMenuAnchorPos: number | undefined;
9
10
  pos: number | null;
10
11
  selectedLayout: PresetLayout | undefined;
11
12
  };
@@ -0,0 +1,18 @@
1
+ import type { Valign } from '@atlaskit/editor-common/types/valign';
2
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
+ import type { Selection } from '@atlaskit/editor-prosemirror/state';
4
+ export type SelectedLayoutColumn = {
5
+ index: number;
6
+ node: PMNode;
7
+ pos: number;
8
+ };
9
+ export type SelectedLayoutColumns = {
10
+ layoutSectionNode: PMNode;
11
+ layoutSectionPos: number;
12
+ selectedColumnIndices: number[];
13
+ selectedColumns: SelectedLayoutColumn[];
14
+ };
15
+ export declare const getSelectedLayoutColumns: (selection: Selection | undefined) => SelectedLayoutColumns | undefined;
16
+ export declare const getLayoutSectionColumnCount: (layoutSection: PMNode | undefined) => number;
17
+ export declare const getLayoutColumnValign: (layoutColumn: PMNode | undefined) => Valign | undefined;
18
+ export declare const getLayoutColumnMenuAnchorPos: (selection: Selection | undefined, anchorPosFromHandle?: number) => number | undefined;
@@ -0,0 +1,4 @@
1
+ import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
2
+ import type { LayoutPlugin } from '../../layoutPluginType';
3
+ import { type SelectedLayoutColumns } from '../../pm-plugins/utils/layout-column-selection';
4
+ export declare const useSelectedLayoutColumns: (api: ExtractInjectionAPI<LayoutPlugin> | undefined) => SelectedLayoutColumns | undefined;