@atlaskit/editor-plugin-layout 10.3.4 → 10.4.0

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 (67) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/cjs/layoutPlugin.js +8 -0
  3. package/dist/cjs/pm-plugins/actions.js +158 -8
  4. package/dist/cjs/pm-plugins/column-resize-divider.js +7 -9
  5. package/dist/cjs/pm-plugins/consts.js +4 -1
  6. package/dist/cjs/pm-plugins/utils/redistribute-proportionally.js +141 -0
  7. package/dist/cjs/ui/LayoutColumnMenu/DeleteColumnDropdownItem.js +42 -0
  8. package/dist/cjs/ui/LayoutColumnMenu/InsertColumnDropdownItem.js +69 -0
  9. package/dist/cjs/ui/LayoutColumnMenu/VerticalAlignDropdownItem.js +7 -9
  10. package/dist/cjs/ui/LayoutColumnMenu/VerticalAlignNestedMenu.js +11 -10
  11. package/dist/cjs/ui/LayoutColumnMenu/components.js +31 -0
  12. package/dist/cjs/ui/LayoutColumnMenu/index.js +39 -11
  13. package/dist/cjs/ui/LayoutColumnMenu/keys.js +14 -2
  14. package/dist/cjs/ui/LayoutColumnMenu/layoutColumnSelection.js +21 -0
  15. package/dist/cjs/ui/LayoutColumnMenu/useCurrentLayoutColumn.js +20 -0
  16. package/dist/es2019/layoutPlugin.js +9 -1
  17. package/dist/es2019/pm-plugins/actions.js +152 -1
  18. package/dist/es2019/pm-plugins/column-resize-divider.js +8 -9
  19. package/dist/es2019/pm-plugins/consts.js +3 -0
  20. package/dist/es2019/pm-plugins/utils/redistribute-proportionally.js +113 -0
  21. package/dist/es2019/ui/LayoutColumnMenu/DeleteColumnDropdownItem.js +37 -0
  22. package/dist/es2019/ui/LayoutColumnMenu/InsertColumnDropdownItem.js +64 -0
  23. package/dist/es2019/ui/LayoutColumnMenu/VerticalAlignDropdownItem.js +6 -11
  24. package/dist/es2019/ui/LayoutColumnMenu/VerticalAlignNestedMenu.js +7 -10
  25. package/dist/es2019/ui/LayoutColumnMenu/components.js +32 -1
  26. package/dist/es2019/ui/LayoutColumnMenu/index.js +34 -13
  27. package/dist/es2019/ui/LayoutColumnMenu/keys.js +17 -2
  28. package/dist/es2019/ui/LayoutColumnMenu/layoutColumnSelection.js +9 -0
  29. package/dist/es2019/ui/LayoutColumnMenu/useCurrentLayoutColumn.js +10 -0
  30. package/dist/esm/layoutPlugin.js +9 -1
  31. package/dist/esm/pm-plugins/actions.js +156 -7
  32. package/dist/esm/pm-plugins/column-resize-divider.js +8 -9
  33. package/dist/esm/pm-plugins/consts.js +3 -0
  34. package/dist/esm/pm-plugins/utils/redistribute-proportionally.js +134 -0
  35. package/dist/esm/ui/LayoutColumnMenu/DeleteColumnDropdownItem.js +35 -0
  36. package/dist/esm/ui/LayoutColumnMenu/InsertColumnDropdownItem.js +61 -0
  37. package/dist/esm/ui/LayoutColumnMenu/VerticalAlignDropdownItem.js +8 -10
  38. package/dist/esm/ui/LayoutColumnMenu/VerticalAlignNestedMenu.js +9 -9
  39. package/dist/esm/ui/LayoutColumnMenu/components.js +32 -1
  40. package/dist/esm/ui/LayoutColumnMenu/index.js +40 -13
  41. package/dist/esm/ui/LayoutColumnMenu/keys.js +13 -1
  42. package/dist/esm/ui/LayoutColumnMenu/layoutColumnSelection.js +15 -0
  43. package/dist/esm/ui/LayoutColumnMenu/useCurrentLayoutColumn.js +14 -0
  44. package/dist/types/layoutPluginType.d.ts +4 -2
  45. package/dist/types/pm-plugins/actions.d.ts +7 -0
  46. package/dist/types/pm-plugins/consts.d.ts +3 -0
  47. package/dist/types/pm-plugins/utils/redistribute-proportionally.d.ts +2 -0
  48. package/dist/types/ui/LayoutColumnMenu/DeleteColumnDropdownItem.d.ts +8 -0
  49. package/dist/types/ui/LayoutColumnMenu/InsertColumnDropdownItem.d.ts +10 -0
  50. package/dist/types/ui/LayoutColumnMenu/keys.d.ts +3 -0
  51. package/dist/types/ui/LayoutColumnMenu/layoutColumnSelection.d.ts +7 -0
  52. package/dist/types/ui/LayoutColumnMenu/useCurrentLayoutColumn.d.ts +5 -0
  53. package/dist/types-ts4.5/layoutPluginType.d.ts +4 -2
  54. package/dist/types-ts4.5/pm-plugins/actions.d.ts +7 -0
  55. package/dist/types-ts4.5/pm-plugins/consts.d.ts +3 -0
  56. package/dist/types-ts4.5/pm-plugins/utils/redistribute-proportionally.d.ts +2 -0
  57. package/dist/types-ts4.5/ui/LayoutColumnMenu/DeleteColumnDropdownItem.d.ts +8 -0
  58. package/dist/types-ts4.5/ui/LayoutColumnMenu/InsertColumnDropdownItem.d.ts +10 -0
  59. package/dist/types-ts4.5/ui/LayoutColumnMenu/keys.d.ts +3 -0
  60. package/dist/types-ts4.5/ui/LayoutColumnMenu/layoutColumnSelection.d.ts +7 -0
  61. package/dist/types-ts4.5/ui/LayoutColumnMenu/useCurrentLayoutColumn.d.ts +5 -0
  62. package/package.json +4 -4
  63. package/dist/cjs/ui/LayoutColumnMenu/useCurrentLayoutColumnValign.js +0 -22
  64. package/dist/es2019/ui/LayoutColumnMenu/useCurrentLayoutColumnValign.js +0 -14
  65. package/dist/esm/ui/LayoutColumnMenu/useCurrentLayoutColumnValign.js +0 -16
  66. package/dist/types/ui/LayoutColumnMenu/useCurrentLayoutColumnValign.d.ts +0 -11
  67. package/dist/types-ts4.5/ui/LayoutColumnMenu/useCurrentLayoutColumnValign.d.ts +0 -11
@@ -0,0 +1,64 @@
1
+ import React, { useCallback } from 'react';
2
+ import { useIntl } from 'react-intl';
3
+ import { layoutMessages } from '@atlaskit/editor-common/messages';
4
+ import { TableColumnAddLeftIcon, TableColumnAddRightIcon, ToolbarDropdownItem } from '@atlaskit/editor-toolbar';
5
+ import { getEffectiveMaxLayoutColumns } from '../../pm-plugins/actions';
6
+ import { getLayoutSectionColumnCount } from './layoutColumnSelection';
7
+ import { useCurrentLayoutColumn, useCurrentLayoutSection } from './useCurrentLayoutColumn';
8
+ const INSERT_COLUMN_OPTIONS = {
9
+ left: {
10
+ Icon: TableColumnAddLeftIcon,
11
+ label: layoutMessages.insertColumnLeft,
12
+ side: 'left'
13
+ },
14
+ right: {
15
+ Icon: TableColumnAddRightIcon,
16
+ label: layoutMessages.insertColumnRight,
17
+ side: 'right'
18
+ }
19
+ };
20
+ export const InsertColumnDropdownItem = ({
21
+ api,
22
+ side
23
+ }) => {
24
+ const {
25
+ formatMessage
26
+ } = useIntl();
27
+ const {
28
+ Icon,
29
+ label
30
+ } = INSERT_COLUMN_OPTIONS[side];
31
+ const currentColumn = useCurrentLayoutColumn(api);
32
+ const currentLayoutSection = useCurrentLayoutSection(api);
33
+ const columnCount = getLayoutSectionColumnCount(currentLayoutSection);
34
+ const maxColumnCount = getEffectiveMaxLayoutColumns();
35
+ const canInsertColumn = currentColumn !== undefined && columnCount < maxColumnCount;
36
+ const onClick = useCallback(() => {
37
+ var _api$layout, _api$core;
38
+ const insertCommand = api === null || api === void 0 ? void 0 : (_api$layout = api.layout) === null || _api$layout === void 0 ? void 0 : _api$layout.commands.insertLayoutColumn(side);
39
+ api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(props => {
40
+ var _api$layout2;
41
+ const tr = insertCommand === null || insertCommand === void 0 ? void 0 : insertCommand(props);
42
+ if (!tr) {
43
+ return tr !== null && tr !== void 0 ? tr : null;
44
+ }
45
+ api === null || api === void 0 ? void 0 : (_api$layout2 = api.layout) === null || _api$layout2 === void 0 ? void 0 : _api$layout2.commands.toggleLayoutColumnMenu({
46
+ isOpen: false
47
+ })({
48
+ tr
49
+ });
50
+ return tr;
51
+ });
52
+ }, [api, side]);
53
+ if (!canInsertColumn) {
54
+ return null;
55
+ }
56
+ return /*#__PURE__*/React.createElement(ToolbarDropdownItem, {
57
+ elemBefore: /*#__PURE__*/React.createElement(Icon, {
58
+ color: "currentColor",
59
+ label: "",
60
+ size: "small"
61
+ }),
62
+ onClick: onClick
63
+ }, formatMessage(label));
64
+ };
@@ -1,7 +1,8 @@
1
- import React, { useCallback } from 'react';
1
+ import React, { useCallback, useMemo } from 'react';
2
2
  import { useIntl } from 'react-intl';
3
3
  import { ToolbarDropdownItem } from '@atlaskit/editor-toolbar';
4
- import { useCurrentLayoutColumnValign } from './useCurrentLayoutColumnValign';
4
+ import { getLayoutColumnValign } from './layoutColumnSelection';
5
+ import { useCurrentLayoutColumn } from './useCurrentLayoutColumn';
5
6
  import { VERTICAL_ALIGN_ICONS } from './verticalAlignIcons';
6
7
  export const VerticalAlignDropdownItem = ({
7
8
  api,
@@ -11,25 +12,19 @@ export const VerticalAlignDropdownItem = ({
11
12
  const {
12
13
  formatMessage
13
14
  } = useIntl();
14
- const {
15
- currentValign,
16
- selectedColumn
17
- } = useCurrentLayoutColumnValign(api);
15
+ const currentColumn = useCurrentLayoutColumn(api);
16
+ const currentValign = useMemo(() => getLayoutColumnValign(currentColumn), [currentColumn]);
18
17
  const Icon = VERTICAL_ALIGN_ICONS[value];
19
18
  const onClick = useCallback(() => {
20
19
  var _api$core, _api$layout;
21
20
  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.setLayoutColumnValign(value));
22
21
  }, [api, value]);
23
- if (!selectedColumn) {
24
- return null;
25
- }
26
22
  return /*#__PURE__*/React.createElement(ToolbarDropdownItem, {
27
23
  elemBefore: /*#__PURE__*/React.createElement(Icon, {
28
24
  label: "",
29
25
  size: "small"
30
26
  }),
31
27
  isSelected: currentValign === value,
32
- onClick: onClick,
33
- testId: `layout-column-align-${value}`
28
+ onClick: onClick
34
29
  }, formatMessage(label));
35
30
  };
@@ -1,8 +1,9 @@
1
- import React from 'react';
1
+ 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 { useCurrentLayoutColumnValign } from './useCurrentLayoutColumnValign';
5
+ import { getLayoutColumnValign } from './layoutColumnSelection';
6
+ import { useCurrentLayoutColumn } from './useCurrentLayoutColumn';
6
7
  import { VERTICAL_ALIGN_ICONS } from './verticalAlignIcons';
7
8
  export const VerticalAlignNestedMenu = ({
8
9
  api,
@@ -11,12 +12,10 @@ export const VerticalAlignNestedMenu = ({
11
12
  const {
12
13
  formatMessage
13
14
  } = useIntl();
14
- const {
15
- currentValign,
16
- selectedColumn
17
- } = useCurrentLayoutColumnValign(api);
15
+ const currentColumn = useCurrentLayoutColumn(api);
16
+ const currentValign = useMemo(() => getLayoutColumnValign(currentColumn), [currentColumn]);
18
17
  const TriggerIcon = currentValign ? VERTICAL_ALIGN_ICONS[currentValign] : LayoutIcon;
19
- if (!selectedColumn) {
18
+ if (!currentColumn) {
20
19
  return null;
21
20
  }
22
21
  return /*#__PURE__*/React.createElement(ToolbarNestedDropdownMenu, {
@@ -28,8 +27,6 @@ export const VerticalAlignNestedMenu = ({
28
27
  label: "",
29
28
  size: "small"
30
29
  }),
31
- text: formatMessage(layoutMessages.alignColumn),
32
- testId: "layout-column-align-menu",
33
- dropdownTestId: "layout-column-align-dropdown"
30
+ text: formatMessage(layoutMessages.alignColumn)
34
31
  }, children);
35
32
  };
@@ -2,8 +2,10 @@ import React from 'react';
2
2
  import { layoutMessages } from '@atlaskit/editor-common/messages';
3
3
  import { ToolbarDropdownItemSection } from '@atlaskit/editor-toolbar';
4
4
  import { ToolbarMenuContainer } from '@atlaskit/editor-toolbar/toolbar-menu-container';
5
+ import { DeleteColumnDropdownItem } from './DeleteColumnDropdownItem';
5
6
  import { createDistributeColumnsDropdownItem } from './DistributeColumnsDropdownItem';
6
- import { DISTRIBUTE_COLUMNS_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';
7
+ import { InsertColumnDropdownItem } from './InsertColumnDropdownItem';
8
+ 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';
7
9
  import { VerticalAlignDropdownItem } from './VerticalAlignDropdownItem';
8
10
  import { VerticalAlignNestedMenu } from './VerticalAlignNestedMenu';
9
11
  export const LAYOUT_COLUMN_MENU_FALLBACKS = {
@@ -23,6 +25,26 @@ export const getLayoutColumnMenuComponents = ({
23
25
  ...LAYOUT_COLUMN_MENU,
24
26
  rank: LAYOUT_COLUMN_MENU_RANK[LAYOUT_COLUMN_MENU_SECTION.key]
25
27
  }]
28
+ }, {
29
+ ...INSERT_COLUMN_LEFT_MENU_ITEM,
30
+ component: () => /*#__PURE__*/React.createElement(InsertColumnDropdownItem, {
31
+ api: api,
32
+ side: "left"
33
+ }),
34
+ parents: [{
35
+ ...LAYOUT_COLUMN_MENU_SECTION,
36
+ rank: LAYOUT_COLUMN_MENU_SECTION_RANK[INSERT_COLUMN_LEFT_MENU_ITEM.key]
37
+ }]
38
+ }, {
39
+ ...INSERT_COLUMN_RIGHT_MENU_ITEM,
40
+ component: () => /*#__PURE__*/React.createElement(InsertColumnDropdownItem, {
41
+ api: api,
42
+ side: "right"
43
+ }),
44
+ parents: [{
45
+ ...LAYOUT_COLUMN_MENU_SECTION,
46
+ rank: LAYOUT_COLUMN_MENU_SECTION_RANK[INSERT_COLUMN_RIGHT_MENU_ITEM.key]
47
+ }]
26
48
  }, {
27
49
  ...DISTRIBUTE_COLUMNS_MENU_ITEM,
28
50
  component: createDistributeColumnsDropdownItem(api),
@@ -30,6 +52,15 @@ export const getLayoutColumnMenuComponents = ({
30
52
  ...LAYOUT_COLUMN_MENU_SECTION,
31
53
  rank: LAYOUT_COLUMN_MENU_SECTION_RANK[DISTRIBUTE_COLUMNS_MENU_ITEM.key]
32
54
  }]
55
+ }, {
56
+ ...DELETE_COLUMN_MENU_ITEM,
57
+ component: () => /*#__PURE__*/React.createElement(DeleteColumnDropdownItem, {
58
+ api: api
59
+ }),
60
+ parents: [{
61
+ ...LAYOUT_COLUMN_MENU_SECTION,
62
+ rank: LAYOUT_COLUMN_MENU_SECTION_RANK[DELETE_COLUMN_MENU_ITEM.key]
63
+ }]
33
64
  }, {
34
65
  ...VERTICAL_ALIGN_MENU,
35
66
  component: ({
@@ -1,25 +1,33 @@
1
- import React, { useCallback, useContext, useEffect, useRef } from 'react';
1
+ import React, { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
2
2
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
3
+ import { DRAG_HANDLE_SELECTOR } from '@atlaskit/editor-common/styles';
3
4
  import { EditorToolbarProvider } from '@atlaskit/editor-common/toolbar';
4
5
  import { Popup } from '@atlaskit/editor-common/ui';
5
6
  import { OutsideClickTargetRefContext, withReactEditorViewOuterListeners } from '@atlaskit/editor-common/ui-react';
6
- import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils';
7
- import { akEditorFloatingPanelZIndex } from '@atlaskit/editor-shared-styles';
7
+ import { akEditorFloatingOverlapPanelZIndex } from '@atlaskit/editor-shared-styles';
8
8
  import { ToolbarDropdownMenuProvider } from '@atlaskit/editor-toolbar';
9
9
  import { SurfaceRenderer } from '@atlaskit/editor-ui-control-model';
10
10
  import { LAYOUT_COLUMN_MENU_FALLBACKS } from './components';
11
11
  import { LAYOUT_COLUMN_MENU } from './keys';
12
+ import { getLayoutColumnAtSelection } from './layoutColumnSelection';
12
13
  const PopupWithListeners = withReactEditorViewOuterListeners(Popup);
13
- const LAYOUT_COLUMN_MENU_POPUP_OFFSET = [0, 10];
14
- const getLayoutColumnMenuTarget = (editorView, selectionAnchorPos) => {
15
- if (selectionAnchorPos === undefined) {
14
+ const FALLBACK_MENU_HEIGHT = 300;
15
+ const LAYOUT_COLUMN_MENU_POPUP_OFFSET = [0, 4];
16
+
17
+ /**
18
+ * Returns the drag handle button for the selected layout column.
19
+ */
20
+ const getLayoutColumnMenuTarget = (editorView, selection) => {
21
+ var _columnDomRef$parentE;
22
+ if (!getLayoutColumnAtSelection(selection) || (selection === null || selection === void 0 ? void 0 : selection.from) === undefined) {
16
23
  return null;
17
24
  }
18
- const selectionNode = editorView.state.doc.nodeAt(selectionAnchorPos);
19
- if ((selectionNode === null || selectionNode === void 0 ? void 0 : selectionNode.type.name) !== 'layoutColumn') {
25
+ const columnDomRef = editorView.nodeDOM(selection.from);
26
+ if (!(columnDomRef instanceof HTMLElement)) {
20
27
  return null;
21
28
  }
22
- return findDomRefAtPos(selectionAnchorPos, editorView.domAtPos.bind(editorView));
29
+ const dragHandleContainer = (_columnDomRef$parentE = columnDomRef.parentElement) === null || _columnDomRef$parentE === void 0 ? void 0 : _columnDomRef$parentE.querySelector(':scope > [data-blocks-drag-handle-container]');
30
+ return dragHandleContainer === null || dragHandleContainer === void 0 ? void 0 : dragHandleContainer.querySelector(DRAG_HANDLE_SELECTOR);
23
31
  };
24
32
  export const LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMenu({
25
33
  api,
@@ -41,6 +49,15 @@ export const LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMen
41
49
  });
42
50
  const setOutsideClickTargetRef = useContext(OutsideClickTargetRefContext);
43
51
  const menuRef = useRef(null);
52
+ const popupRef = useRef(undefined);
53
+ const [menuHeight, setMenuHeight] = React.useState(FALLBACK_MENU_HEIGHT);
54
+ useLayoutEffect(() => {
55
+ var _popupRef$current;
56
+ if (!isLayoutColumnMenuOpen) {
57
+ return;
58
+ }
59
+ setMenuHeight(((_popupRef$current = popupRef.current) === null || _popupRef$current === void 0 ? void 0 : _popupRef$current.clientHeight) || FALLBACK_MENU_HEIGHT);
60
+ }, [isLayoutColumnMenuOpen]);
44
61
  const closeLayoutColumnMenu = useCallback(() => {
45
62
  var _api$core, _api$layout;
46
63
  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({
@@ -65,9 +82,12 @@ export const LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMen
65
82
  const handleMenuRef = useCallback(el => {
66
83
  setOutsideClickTargetRef === null || setOutsideClickTargetRef === void 0 ? void 0 : setOutsideClickTargetRef(el);
67
84
  menuRef.current = el;
85
+ if (el) {
86
+ popupRef.current = el;
87
+ }
68
88
  }, [setOutsideClickTargetRef]);
69
89
  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 : [];
70
- const target = isLayoutColumnMenuOpen ? getLayoutColumnMenuTarget(editorView, selection === null || selection === void 0 ? void 0 : selection.from) : null;
90
+ const target = useMemo(() => isLayoutColumnMenuOpen ? getLayoutColumnMenuTarget(editorView, selection) : null, [editorView, isLayoutColumnMenuOpen, selection]);
71
91
  const hasValidTarget = target instanceof HTMLElement;
72
92
  useEffect(() => {
73
93
  if (isLayoutColumnMenuOpen && (!hasValidTarget || components.length === 0)) {
@@ -82,10 +102,11 @@ export const LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMen
82
102
  mountTo: mountTo,
83
103
  boundariesElement: boundariesElement,
84
104
  scrollableElement: scrollableElement,
85
- zIndex: akEditorFloatingPanelZIndex,
105
+ zIndex: akEditorFloatingOverlapPanelZIndex,
86
106
  alignX: "center",
87
- alignY: "top",
88
- forcePlacement: true,
107
+ fitHeight: menuHeight,
108
+ preventOverflow: true,
109
+ stick: true,
89
110
  offset: LAYOUT_COLUMN_MENU_POPUP_OFFSET,
90
111
  handleClickOutside: handleClickOutside,
91
112
  handleEscapeKeydown: closeLayoutColumnMenu
@@ -28,6 +28,14 @@ export const VERTICAL_ALIGN_MENU = {
28
28
 
29
29
  // --- Items ---
30
30
 
31
+ export const INSERT_COLUMN_LEFT_MENU_ITEM = {
32
+ type: 'menu-item',
33
+ key: 'layout-column-menu-insert-left-item'
34
+ };
35
+ export const INSERT_COLUMN_RIGHT_MENU_ITEM = {
36
+ type: 'menu-item',
37
+ key: 'layout-column-menu-insert-right-item'
38
+ };
31
39
  export const DISTRIBUTE_COLUMNS_MENU_ITEM = {
32
40
  type: 'menu-item',
33
41
  key: 'layout-column-menu-distribute-columns-item'
@@ -44,12 +52,19 @@ export const VERTICAL_ALIGN_BOTTOM_MENU_ITEM = {
44
52
  type: 'menu-item',
45
53
  key: 'layout-column-menu-vertical-align-bottom-item'
46
54
  };
55
+ export const DELETE_COLUMN_MENU_ITEM = {
56
+ type: 'menu-item',
57
+ key: 'layout-column-menu-delete-item'
58
+ };
47
59
 
48
60
  // --- Item ranks within sections ---
49
61
 
50
62
  export const LAYOUT_COLUMN_MENU_SECTION_RANK = {
51
- [DISTRIBUTE_COLUMNS_MENU_ITEM.key]: 100,
52
- [VERTICAL_ALIGN_MENU.key]: 200
63
+ [VERTICAL_ALIGN_MENU.key]: 100,
64
+ [INSERT_COLUMN_LEFT_MENU_ITEM.key]: 200,
65
+ [INSERT_COLUMN_RIGHT_MENU_ITEM.key]: 300,
66
+ [DISTRIBUTE_COLUMNS_MENU_ITEM.key]: 400,
67
+ [DELETE_COLUMN_MENU_ITEM.key]: 500
53
68
  };
54
69
  export const VERTICAL_ALIGN_MENU_RANK = {
55
70
  [LAYOUT_COLUMN_VERTICAL_ALIGN_MENU_SECTION.key]: 100
@@ -0,0 +1,9 @@
1
+ import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
2
+ export const getLayoutColumnAtSelection = selection => selection instanceof NodeSelection && selection.node.type.name === 'layoutColumn' ? selection.node : undefined;
3
+ export const getLayoutSectionAtSelection = selection => {
4
+ const selectedColumn = getLayoutColumnAtSelection(selection);
5
+ const parent = selectedColumn ? selection === null || selection === void 0 ? void 0 : selection.$from.parent : undefined;
6
+ return (parent === null || parent === void 0 ? void 0 : parent.type.name) === 'layoutSection' ? parent : undefined;
7
+ };
8
+ export const getLayoutSectionColumnCount = layoutSection => (layoutSection === null || layoutSection === void 0 ? void 0 : layoutSection.type.name) === 'layoutSection' ? layoutSection.childCount : 0;
9
+ export const getLayoutColumnValign = layoutColumn => layoutColumn === null || layoutColumn === void 0 ? void 0 : layoutColumn.attrs.valign;
@@ -0,0 +1,10 @@
1
+ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
2
+ import { getLayoutColumnAtSelection, getLayoutSectionAtSelection } from './layoutColumnSelection';
3
+ export const useCurrentLayoutColumn = api => useSharedPluginStateWithSelector(api, ['selection'], states => {
4
+ var _states$selectionStat;
5
+ return getLayoutColumnAtSelection((_states$selectionStat = states.selectionState) === null || _states$selectionStat === void 0 ? void 0 : _states$selectionStat.selection);
6
+ });
7
+ export const useCurrentLayoutSection = api => useSharedPluginStateWithSelector(api, ['selection'], states => {
8
+ var _states$selectionStat2;
9
+ return getLayoutSectionAtSelection((_states$selectionStat2 = states.selectionState) === null || _states$selectionStat2 === void 0 ? void 0 : _states$selectionStat2.selection);
10
+ });
@@ -10,7 +10,7 @@ import { findParentNode } from '@atlaskit/editor-prosemirror/utils';
10
10
  import { fg } from '@atlaskit/platform-feature-flags';
11
11
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
12
12
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
13
- import { createDefaultLayoutSection, createMultiColumnLayoutSection, insertLayoutColumnsWithAnalytics, setLayoutColumnValign, toggleLayoutColumnMenu } from './pm-plugins/actions';
13
+ import { createDefaultLayoutSection, createMultiColumnLayoutSection, deleteLayoutColumn as _deleteLayoutColumn, insertLayoutColumn as _insertLayoutColumn, insertLayoutColumnsWithAnalytics, setLayoutColumnValign, toggleLayoutColumnMenu } from './pm-plugins/actions';
14
14
  import { default as createLayoutPlugin } from './pm-plugins/main';
15
15
  import { pluginKey } from './pm-plugins/plugin-key';
16
16
  import { default as createLayoutResizingPlugin } from './pm-plugins/resizing';
@@ -302,6 +302,14 @@ export var layoutPlugin = function layoutPlugin(_ref) {
302
302
  return pluginKey.getState(editorState);
303
303
  },
304
304
  commands: {
305
+ deleteLayoutColumn: function deleteLayoutColumn(props) {
306
+ var _api$analytics4;
307
+ return _deleteLayoutColumn(api === null || api === void 0 || (_api$analytics4 = api.analytics) === null || _api$analytics4 === void 0 ? void 0 : _api$analytics4.actions)(props);
308
+ },
309
+ insertLayoutColumn: function insertLayoutColumn(side) {
310
+ var _api$analytics5;
311
+ return _insertLayoutColumn(side, api === null || api === void 0 || (_api$analytics5 = api.analytics) === null || _api$analytics5 === void 0 ? void 0 : _api$analytics5.actions);
312
+ },
305
313
  setLayoutColumnValign: setLayoutColumnValign,
306
314
  toggleLayoutColumnMenu: toggleLayoutColumnMenu
307
315
  }
@@ -10,8 +10,9 @@ import { safeInsert } from '@atlaskit/editor-prosemirror/utils';
10
10
  import { fg } from '@atlaskit/platform-feature-flags';
11
11
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
12
12
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
13
- import { EVEN_DISTRIBUTED_COL_WIDTHS } from './consts';
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 { redistributeAfterDeletion, redistributeProportionally } from './utils/redistribute-proportionally';
15
16
  export var ONE_COL_LAYOUTS = ['single'];
16
17
  export var TWO_COL_LAYOUTS = ['two_equal', 'two_left_sidebar', 'two_right_sidebar'];
17
18
  export var THREE_COL_LAYOUTS = ['three_equal', 'three_with_sidebars'];
@@ -540,9 +541,102 @@ var formatLayoutName = function formatLayoutName(layout) {
540
541
  return LAYOUT_TYPE.THREE_WITH_SIDEBARS;
541
542
  }
542
543
  };
544
+ /**
545
+ * Returns the active maximum layout column count for the current advanced layouts experiment state.
546
+ */
547
+ export function getEffectiveMaxLayoutColumns() {
548
+ return editorExperiment('advanced_layouts', true) ? MAX_LAYOUT_COLUMNS : MAX_STANDARD_LAYOUT_COLUMNS;
549
+ }
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
+ var insertLayoutColumnAt = function insertLayoutColumnAt(side, editorAnalyticsAPI) {
576
+ return function (_ref5) {
577
+ var tr = _ref5.tr;
578
+ if (!expValEqualsNoExposure('platform_editor_layout_column_menu', 'isEnabled', true)) {
579
+ return null;
580
+ }
581
+ var selectedColumn = getSelectedLayoutColumnInSection(tr);
582
+ if (!selectedColumn) {
583
+ return null;
584
+ }
585
+ var layoutSectionNode = selectedColumn.layoutSectionNode,
586
+ layoutSectionPos = selectedColumn.layoutSectionPos,
587
+ selectedColumnIndex = selectedColumn.selectedColumnIndex;
588
+ if (layoutSectionNode.childCount >= getEffectiveMaxLayoutColumns()) {
589
+ return null;
590
+ }
591
+ var insertIndex = side === 'left' ? selectedColumnIndex : selectedColumnIndex + 1;
592
+ var existingWidths = mapChildren(layoutSectionNode, function (column) {
593
+ return column.attrs.width;
594
+ });
595
+ var redistributedWidths = redistributeProportionally(existingWidths, insertIndex, getEffectiveMaxLayoutColumns(), MIN_LAYOUT_COLUMN_WIDTH_PERCENT);
596
+ if (redistributedWidths === existingWidths) {
597
+ return null;
598
+ }
599
+ var layoutColumn = tr.doc.type.schema.nodes.layoutColumn;
600
+ var newColumn = layoutColumn.createAndFill({
601
+ width: redistributedWidths[insertIndex]
602
+ });
603
+ if (!newColumn) {
604
+ return null;
605
+ }
606
+ var updatedColumns = [];
607
+ layoutSectionNode.forEach(function (column, _offset, index) {
608
+ if (index === insertIndex) {
609
+ updatedColumns.push(newColumn);
610
+ }
611
+ updatedColumns.push(column);
612
+ });
613
+ if (insertIndex === layoutSectionNode.childCount) {
614
+ updatedColumns.push(newColumn);
615
+ }
616
+ var updatedLayoutSectionNode = layoutSectionNode.copy(Fragment.fromArray(updatedColumns));
617
+ tr.replaceWith(layoutSectionPos + 1, layoutSectionPos + layoutSectionNode.nodeSize - 1, columnWidth(updatedLayoutSectionNode, tr.doc.type.schema, redistributedWidths));
618
+ editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
619
+ action: ACTION.INSERTED,
620
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
621
+ actionSubjectId: ACTION_SUBJECT_ID.LAYOUT_COLUMN,
622
+ attributes: {
623
+ columnCount: redistributedWidths.length,
624
+ inputMethod: INPUT_METHOD.LAYOUT_COLUMN_MENU,
625
+ selectedIndex: selectedColumnIndex,
626
+ side: side
627
+ },
628
+ eventType: EVENT_TYPE.TRACK
629
+ })(tr);
630
+ tr.setMeta('scrollIntoView', false);
631
+ return tr;
632
+ };
633
+ };
634
+ export var insertLayoutColumn = function insertLayoutColumn(side, editorAnalyticsAPI) {
635
+ return insertLayoutColumnAt(side, editorAnalyticsAPI);
636
+ };
543
637
  export var setLayoutColumnValign = function setLayoutColumnValign(valign) {
544
- return function (_ref4) {
545
- var tr = _ref4.tr;
638
+ return function (_ref6) {
639
+ var tr = _ref6.tr;
546
640
  if (!expValEqualsNoExposure('platform_editor_layout_column_menu', 'isEnabled', true)) {
547
641
  return null;
548
642
  }
@@ -564,14 +658,69 @@ export var setLayoutColumnValign = function setLayoutColumnValign(valign) {
564
658
  return tr;
565
659
  };
566
660
  };
567
- export var toggleLayoutColumnMenu = function toggleLayoutColumnMenu(_ref5) {
568
- var isOpen = _ref5.isOpen;
569
- return function (_ref6) {
570
- var tr = _ref6.tr;
661
+ export var toggleLayoutColumnMenu = function toggleLayoutColumnMenu(_ref7) {
662
+ var isOpen = _ref7.isOpen;
663
+ return function (_ref8) {
664
+ var tr = _ref8.tr;
571
665
  tr.setMeta('toggleLayoutColumnMenu', {
572
666
  isOpen: isOpen
573
667
  });
574
668
  tr.setMeta('scrollIntoView', false);
575
669
  return tr;
576
670
  };
671
+ };
672
+ export var deleteLayoutColumn = function deleteLayoutColumn(editorAnalyticsAPI) {
673
+ return function (_ref9) {
674
+ var tr = _ref9.tr;
675
+ if (!expValEqualsNoExposure('platform_editor_layout_column_menu', 'isEnabled', true)) {
676
+ return null;
677
+ }
678
+ var selectedColumn = getSelectedLayoutColumnInSection(tr);
679
+ if (!selectedColumn) {
680
+ return null;
681
+ }
682
+ var layoutSectionNode = selectedColumn.layoutSectionNode,
683
+ layoutSectionPos = selectedColumn.layoutSectionPos,
684
+ selectedColumnIndex = selectedColumn.selectedColumnIndex;
685
+ var emitDeleteColumnAnalytics = function emitDeleteColumnAnalytics(columnCount) {
686
+ editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
687
+ action: ACTION.DELETED,
688
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
689
+ actionSubjectId: ACTION_SUBJECT_ID.LAYOUT_COLUMN,
690
+ attributes: {
691
+ columnCount: columnCount,
692
+ inputMethod: INPUT_METHOD.LAYOUT_COLUMN_MENU,
693
+ selectedIndex: selectedColumnIndex
694
+ },
695
+ eventType: EVENT_TYPE.TRACK
696
+ })(tr);
697
+ };
698
+
699
+ // If only one column remains, remove the entire layoutSection
700
+ if (layoutSectionNode.childCount === 1) {
701
+ tr.delete(layoutSectionPos, layoutSectionPos + layoutSectionNode.nodeSize);
702
+ emitDeleteColumnAnalytics(0);
703
+ tr.setMeta('scrollIntoView', false);
704
+ return tr;
705
+ }
706
+
707
+ // Build new column list without the selected column
708
+ var remainingColumns = [];
709
+ layoutSectionNode.forEach(function (column, _offset, index) {
710
+ if (index !== selectedColumnIndex) {
711
+ remainingColumns.push(column);
712
+ }
713
+ });
714
+
715
+ // Redistribute widths proportionally among remaining columns using shared utility
716
+ var existingWidths = mapChildren(layoutSectionNode, function (column) {
717
+ return column.attrs.width;
718
+ });
719
+ var redistributed = redistributeAfterDeletion(existingWidths, selectedColumnIndex, MIN_LAYOUT_COLUMN_WIDTH_PERCENT);
720
+ var updatedLayoutSectionNode = layoutSectionNode.copy(Fragment.fromArray(remainingColumns));
721
+ tr.replaceWith(layoutSectionPos + 1, layoutSectionPos + layoutSectionNode.nodeSize - 1, columnWidth(updatedLayoutSectionNode, tr.doc.type.schema, redistributed));
722
+ emitDeleteColumnAnalytics(redistributed.length);
723
+ tr.setMeta('scrollIntoView', false);
724
+ return tr;
725
+ };
577
726
  };
@@ -4,14 +4,13 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
4
4
  import { bind } from 'bind-event-listener';
5
5
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
6
6
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
7
+ import { MIN_LAYOUT_COLUMN_WIDTH_PERCENT } from './consts';
8
+
7
9
  // Class names for the column resize divider widget — must stay in sync with layout.ts in editor-core
8
10
  var layoutColumnDividerClassName = 'layout-column-divider';
9
11
  var layoutColumnDividerRailClassName = 'layout-column-divider-rail';
10
12
  var layoutColumnDividerThumbClassName = 'layout-column-divider-thumb';
11
13
 
12
- // Minimum column width percentage to prevent columns from collapsing
13
- var MIN_COLUMN_WIDTH_PERCENT = 5;
14
-
15
14
  // Module-level drag state so it survives widget DOM recreation during transactions.
16
15
  var dragState = null;
17
16
 
@@ -72,12 +71,12 @@ var calcDragWidths = function calcDragWidths(clientX) {
72
71
  var deltaPercent = deltaX / columnsWidth * 100;
73
72
  var leftWidth = dragState.startLeftWidth + deltaPercent;
74
73
  var rightWidth = dragState.startRightWidth - deltaPercent;
75
- if (leftWidth < MIN_COLUMN_WIDTH_PERCENT) {
76
- leftWidth = MIN_COLUMN_WIDTH_PERCENT;
77
- rightWidth = combinedWidth - MIN_COLUMN_WIDTH_PERCENT;
78
- } else if (rightWidth < MIN_COLUMN_WIDTH_PERCENT) {
79
- rightWidth = MIN_COLUMN_WIDTH_PERCENT;
80
- leftWidth = combinedWidth - MIN_COLUMN_WIDTH_PERCENT;
74
+ if (leftWidth < MIN_LAYOUT_COLUMN_WIDTH_PERCENT) {
75
+ leftWidth = MIN_LAYOUT_COLUMN_WIDTH_PERCENT;
76
+ rightWidth = combinedWidth - MIN_LAYOUT_COLUMN_WIDTH_PERCENT;
77
+ } else if (rightWidth < MIN_LAYOUT_COLUMN_WIDTH_PERCENT) {
78
+ rightWidth = MIN_LAYOUT_COLUMN_WIDTH_PERCENT;
79
+ leftWidth = combinedWidth - MIN_LAYOUT_COLUMN_WIDTH_PERCENT;
81
80
  }
82
81
  return {
83
82
  leftWidth: leftWidth,
@@ -1,3 +1,6 @@
1
+ export var MAX_STANDARD_LAYOUT_COLUMNS = 3;
2
+ export var MAX_LAYOUT_COLUMNS = 5;
3
+ export var MIN_LAYOUT_COLUMN_WIDTH_PERCENT = 5;
1
4
  export var EVEN_DISTRIBUTED_COL_WIDTHS = {
2
5
  1: 100,
3
6
  2: 50,