@atlaskit/editor-plugin-layout 11.0.4 → 11.1.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 (39) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cjs/layoutPlugin.js +19 -4
  3. package/dist/cjs/pm-plugins/actions.js +30 -10
  4. package/dist/cjs/pm-plugins/keymap.js +31 -0
  5. package/dist/cjs/pm-plugins/main.js +90 -21
  6. package/dist/cjs/pm-plugins/utils/layout-column-selection.js +36 -12
  7. package/dist/cjs/ui/LayoutColumnMenu/DeleteColumnDropdownItem.js +25 -5
  8. package/dist/cjs/ui/LayoutColumnMenu/InsertColumnDropdownItem.js +7 -1
  9. package/dist/cjs/ui/LayoutColumnMenu/index.js +51 -4
  10. package/dist/cjs/ui/global-styles.js +11 -1
  11. package/dist/es2019/layoutPlugin.js +18 -5
  12. package/dist/es2019/pm-plugins/actions.js +26 -11
  13. package/dist/es2019/pm-plugins/keymap.js +26 -0
  14. package/dist/es2019/pm-plugins/main.js +97 -25
  15. package/dist/es2019/pm-plugins/utils/layout-column-selection.js +33 -8
  16. package/dist/es2019/ui/LayoutColumnMenu/DeleteColumnDropdownItem.js +26 -6
  17. package/dist/es2019/ui/LayoutColumnMenu/InsertColumnDropdownItem.js +8 -2
  18. package/dist/es2019/ui/LayoutColumnMenu/index.js +52 -5
  19. package/dist/es2019/ui/global-styles.js +9 -1
  20. package/dist/esm/layoutPlugin.js +20 -5
  21. package/dist/esm/pm-plugins/actions.js +30 -10
  22. package/dist/esm/pm-plugins/keymap.js +25 -0
  23. package/dist/esm/pm-plugins/main.js +90 -21
  24. package/dist/esm/pm-plugins/utils/layout-column-selection.js +35 -11
  25. package/dist/esm/ui/LayoutColumnMenu/DeleteColumnDropdownItem.js +26 -6
  26. package/dist/esm/ui/LayoutColumnMenu/InsertColumnDropdownItem.js +8 -2
  27. package/dist/esm/ui/LayoutColumnMenu/index.js +52 -5
  28. package/dist/esm/ui/global-styles.js +11 -1
  29. package/dist/types/layoutPluginType.d.ts +5 -7
  30. package/dist/types/pm-plugins/actions.d.ts +8 -4
  31. package/dist/types/pm-plugins/keymap.d.ts +10 -0
  32. package/dist/types/pm-plugins/types.d.ts +2 -0
  33. package/dist/types/pm-plugins/utils/layout-column-selection.d.ts +1 -0
  34. package/dist/types-ts4.5/layoutPluginType.d.ts +5 -7
  35. package/dist/types-ts4.5/pm-plugins/actions.d.ts +8 -4
  36. package/dist/types-ts4.5/pm-plugins/keymap.d.ts +10 -0
  37. package/dist/types-ts4.5/pm-plugins/types.d.ts +2 -0
  38. package/dist/types-ts4.5/pm-plugins/utils/layout-column-selection.d.ts +1 -0
  39. package/package.json +4 -4
@@ -1,7 +1,8 @@
1
1
  import React, { useCallback } from 'react';
2
2
  import { useIntl } from 'react-intl';
3
+ import { addColumnAfter, addColumnBefore, getAriaKeyshortcuts, tooltip } from '@atlaskit/editor-common/keymaps';
3
4
  import { layoutMessages } from '@atlaskit/editor-common/messages';
4
- import { TableColumnAddLeftIcon, TableColumnAddRightIcon, ToolbarDropdownItem } from '@atlaskit/editor-toolbar';
5
+ import { TableColumnAddLeftIcon, TableColumnAddRightIcon, ToolbarDropdownItem, ToolbarKeyboardShortcutHint } from '@atlaskit/editor-toolbar';
5
6
  import { getEffectiveMaxLayoutColumns } from '../../pm-plugins/actions';
6
7
  import { useSelectedLayoutColumns } from './useSelectedLayoutColumns';
7
8
  const INSERT_COLUMN_OPTIONS = {
@@ -20,7 +21,7 @@ export const InsertColumnDropdownItem = ({
20
21
  api,
21
22
  side
22
23
  }) => {
23
- var _selectedLayoutColumn, _selectedLayoutColumn2;
24
+ var _selectedLayoutColumn, _selectedLayoutColumn2, _tooltip;
24
25
  const {
25
26
  formatMessage
26
27
  } = useIntl();
@@ -28,6 +29,7 @@ export const InsertColumnDropdownItem = ({
28
29
  Icon,
29
30
  label
30
31
  } = INSERT_COLUMN_OPTIONS[side];
32
+ const shortcut = side === 'left' ? addColumnBefore : addColumnAfter;
31
33
  const selectedLayoutColumns = useSelectedLayoutColumns(api);
32
34
  const columnCount = (_selectedLayoutColumn = selectedLayoutColumns === null || selectedLayoutColumns === void 0 ? void 0 : (_selectedLayoutColumn2 = selectedLayoutColumns.layoutSectionNode) === null || _selectedLayoutColumn2 === void 0 ? void 0 : _selectedLayoutColumn2.childCount) !== null && _selectedLayoutColumn !== void 0 ? _selectedLayoutColumn : 0;
33
35
  const maxColumnCount = getEffectiveMaxLayoutColumns();
@@ -53,11 +55,15 @@ export const InsertColumnDropdownItem = ({
53
55
  return null;
54
56
  }
55
57
  return /*#__PURE__*/React.createElement(ToolbarDropdownItem, {
58
+ ariaKeyshortcuts: getAriaKeyshortcuts(shortcut),
56
59
  elemBefore: /*#__PURE__*/React.createElement(Icon, {
57
60
  color: "currentColor",
58
61
  label: "",
59
62
  size: "small"
60
63
  }),
64
+ elemAfter: /*#__PURE__*/React.createElement(ToolbarKeyboardShortcutHint, {
65
+ shortcut: (_tooltip = tooltip(shortcut)) !== null && _tooltip !== void 0 ? _tooltip : ''
66
+ }),
61
67
  onClick: onClick
62
68
  }, formatMessage(label));
63
69
  };
@@ -1,7 +1,9 @@
1
- import React, { useCallback, useEffect, useMemo } from 'react';
1
+ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
2
+ import { bind } from 'bind-event-listener';
2
3
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
3
4
  import { DRAG_HANDLE_SELECTOR } from '@atlaskit/editor-common/styles';
4
5
  import { Popup } from '@atlaskit/editor-common/ui';
6
+ import { ArrowKeyNavigationProvider, ArrowKeyNavigationType } from '@atlaskit/editor-common/ui-menu';
5
7
  import { withReactEditorViewOuterListeners } from '@atlaskit/editor-common/ui-react';
6
8
  import { UserIntentPopupWrapper } from '@atlaskit/editor-common/user-intent';
7
9
  import { akEditorFloatingOverlapPanelZIndex } from '@atlaskit/editor-shared-styles';
@@ -13,6 +15,7 @@ import { LAYOUT_COLUMN_MENU } from './keys';
13
15
  const PopupWithListeners = withReactEditorViewOuterListeners(Popup);
14
16
  const LAYOUT_COLUMN_MENU_POPUP_OFFSET = [0, 4];
15
17
  const TOOLBAR_MENU_SELECTOR = '[data-toolbar-component="menu"]';
18
+ const NESTED_DROPDOWN_MENU_SELECTOR = '[data-toolbar-nested-dropdown-menu]';
16
19
 
17
20
  /**
18
21
  * Returns the drag handle button for the selected layout column.
@@ -30,6 +33,9 @@ const getLayoutColumnMenuTarget = (editorView, selection, anchorPosFromHandle) =
30
33
  const dragHandleContainer = (_columnDomRef$parentE = columnDomRef.parentElement) === null || _columnDomRef$parentE === void 0 ? void 0 : _columnDomRef$parentE.querySelector(':scope > [data-blocks-drag-handle-container]');
31
34
  return dragHandleContainer === null || dragHandleContainer === void 0 ? void 0 : dragHandleContainer.querySelector(DRAG_HANDLE_SELECTOR);
32
35
  };
36
+ const focusTrap = {
37
+ initialFocus: undefined
38
+ };
33
39
  export const LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMenu({
34
40
  api,
35
41
  editorView,
@@ -41,12 +47,14 @@ export const LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMen
41
47
  const {
42
48
  isLayoutColumnMenuOpen,
43
49
  layoutColumnMenuAnchorPos,
50
+ openedViaKeyboard,
44
51
  selection
45
52
  } = useSharedPluginStateWithSelector(api, ['layout', 'selection'], states => {
46
- var _states$layoutState$i, _states$layoutState, _states$layoutState2, _states$selectionStat;
53
+ var _states$layoutState$i, _states$layoutState, _states$layoutState2, _states$layoutState$l, _states$layoutState3, _states$selectionStat;
47
54
  return {
48
55
  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,
49
56
  layoutColumnMenuAnchorPos: (_states$layoutState2 = states.layoutState) === null || _states$layoutState2 === void 0 ? void 0 : _states$layoutState2.layoutColumnMenuAnchorPos,
57
+ openedViaKeyboard: (_states$layoutState$l = (_states$layoutState3 = states.layoutState) === null || _states$layoutState3 === void 0 ? void 0 : _states$layoutState3.layoutColumnMenuOpenedViaKeyboard) !== null && _states$layoutState$l !== void 0 ? _states$layoutState$l : false,
50
58
  selection: (_states$selectionStat = states.selectionState) === null || _states$selectionStat === void 0 ? void 0 : _states$selectionStat.selection
51
59
  };
52
60
  });
@@ -57,7 +65,7 @@ export const LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMen
57
65
  }));
58
66
  }, [api]);
59
67
  const handleClickOutside = useCallback(event => {
60
- if (event.target instanceof Element && (event.target.closest(TOOLBAR_MENU_SELECTOR) || event.target.closest('[data-toolbar-nested-dropdown-menu]'))) {
68
+ if (event.target instanceof Element && (event.target.closest(TOOLBAR_MENU_SELECTOR) || event.target.closest(NESTED_DROPDOWN_MENU_SELECTOR))) {
61
69
  return;
62
70
  }
63
71
 
@@ -74,6 +82,38 @@ export const LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMen
74
82
  closeLayoutColumnMenu();
75
83
  }
76
84
  }, [closeLayoutColumnMenu]);
85
+ const handleArrowKeyNavigationClose = useCallback(event => {
86
+ event.preventDefault();
87
+ closeLayoutColumnMenu();
88
+ }, [closeLayoutColumnMenu]);
89
+ const shouldDisableArrowKeyNavigation = useCallback(event => {
90
+ if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') {
91
+ return false;
92
+ }
93
+ const target = event.target;
94
+ if (!(target instanceof HTMLElement)) {
95
+ return false;
96
+ }
97
+ return target.closest(NESTED_DROPDOWN_MENU_SELECTOR) !== null;
98
+ }, []);
99
+ const menuWrapperRef = useRef(null);
100
+ const handleMenuKeyDown = useCallback(event => {
101
+ // Keep menu keyboard events scoped to the menu while preserving Escape and
102
+ // ArrowUp/ArrowDown handling from Popup and ArrowKeyNavigationProvider.
103
+ if (event.key !== 'Escape' && event.key !== 'ArrowUp' && event.key !== 'ArrowDown') {
104
+ event.stopPropagation();
105
+ }
106
+ }, []);
107
+ useEffect(() => {
108
+ const menuWrapper = menuWrapperRef.current;
109
+ if (!isLayoutColumnMenuOpen || !menuWrapper) {
110
+ return;
111
+ }
112
+ return bind(menuWrapper, {
113
+ type: 'keydown',
114
+ listener: handleMenuKeyDown
115
+ });
116
+ }, [handleMenuKeyDown, isLayoutColumnMenuOpen]);
77
117
  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 : [];
78
118
  const target = useMemo(() => isLayoutColumnMenuOpen ? getLayoutColumnMenuTarget(editorView, selection, layoutColumnMenuAnchorPos) : null, [editorView, isLayoutColumnMenuOpen, layoutColumnMenuAnchorPos, selection]);
79
119
  const hasValidTarget = target instanceof HTMLElement;
@@ -96,16 +136,23 @@ export const LayoutColumnMenu = /*#__PURE__*/React.memo(function LayoutColumnMen
96
136
  stick: true,
97
137
  offset: LAYOUT_COLUMN_MENU_POPUP_OFFSET,
98
138
  handleClickOutside: handleClickOutside,
99
- handleEscapeKeydown: closeLayoutColumnMenu
139
+ handleEscapeKeydown: closeLayoutColumnMenu,
140
+ focusTrap: openedViaKeyboard ? focusTrap : undefined
141
+ }, /*#__PURE__*/React.createElement("div", {
142
+ ref: menuWrapperRef
100
143
  }, /*#__PURE__*/React.createElement(UserIntentPopupWrapper, {
101
144
  api: api,
102
145
  userIntent: "layoutColumnMenuPopupOpen"
103
146
  }, /*#__PURE__*/React.createElement(ToolbarDropdownMenuProvider, {
104
147
  isOpen: isLayoutColumnMenuOpen,
105
148
  setIsOpen: handleSetIsOpen
149
+ }, /*#__PURE__*/React.createElement(ArrowKeyNavigationProvider, {
150
+ type: ArrowKeyNavigationType.MENU,
151
+ handleClose: handleArrowKeyNavigationClose,
152
+ disableArrowKeyNavigation: shouldDisableArrowKeyNavigation
106
153
  }, /*#__PURE__*/React.createElement(SurfaceRenderer, {
107
154
  components: components,
108
155
  fallbacks: LAYOUT_COLUMN_MENU_FALLBACKS,
109
156
  surface: LAYOUT_COLUMN_MENU
110
- }))));
157
+ }))))));
111
158
  });
@@ -10,6 +10,13 @@ import { useIntl } from 'react-intl';
10
10
  import { layoutMessages as messages } from '@atlaskit/editor-common/messages';
11
11
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
12
12
  const PLACEHOLDER_SELECTOR = '.ProseMirror-focused .layoutSectionView-content-wrap.selected [data-layout-column] > [data-layout-content] > p:only-child:has(.ProseMirror-trailingBreak:only-child)';
13
+ const layoutColumnDangerPreviewStyle = css({
14
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors -- Ignored via go/DSP-18766
15
+ '.ProseMirror [data-layout-column].layout-column-danger-preview': {
16
+ backgroundColor: "var(--ds-background-danger, #FFECEB)",
17
+ boxShadow: `inset 0 0 0 2px ${"var(--ds-border-danger, #E2483D)"}`
18
+ }
19
+ });
13
20
  const getPlaceholderStyle = message => {
14
21
  if (editorExperiment('platform_editor_controls', 'variant1')) {
15
22
  return css({
@@ -52,7 +59,8 @@ export const GlobalStylesWrapper = () => {
52
59
  const placeholderText = editorExperiment('platform_editor_controls', 'variant1') ? messages.controlslayoutPlaceholder : messages.layoutPlaceholder;
53
60
  return getPlaceholderStyle(formatMessage(placeholderText));
54
61
  }, [formatMessage]);
62
+ const globalStyles = useMemo(() => [placeholderStyle, layoutColumnDangerPreviewStyle], [placeholderStyle]);
55
63
  return jsx(Global, {
56
- styles: placeholderStyle
64
+ styles: globalStyles
57
65
  });
58
66
  };
@@ -11,7 +11,8 @@ import { fg } from '@atlaskit/platform-feature-flags';
11
11
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
12
12
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
13
13
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
14
- import { createDefaultLayoutSection, createMultiColumnLayoutSection, deleteLayoutColumn, distributeLayoutColumns as _distributeLayoutColumns, insertLayoutColumn as _insertLayoutColumn, insertLayoutColumnsWithAnalytics, setLayoutColumnValign as _setLayoutColumnValign, toggleLayoutColumnMenu } from './pm-plugins/actions';
14
+ import { createDefaultLayoutSection, createMultiColumnLayoutSection, deleteLayoutColumn as _deleteLayoutColumn, distributeLayoutColumns as _distributeLayoutColumns, insertLayoutColumn as _insertLayoutColumn, insertLayoutColumnsWithAnalytics, setLayoutColumnDangerPreview, setLayoutColumnValign as _setLayoutColumnValign, toggleLayoutColumnMenu } from './pm-plugins/actions';
15
+ import { default as createLayoutKeymapPlugin } from './pm-plugins/keymap';
15
16
  import { default as createLayoutPlugin } from './pm-plugins/main';
16
17
  import { pluginKey } from './pm-plugins/plugin-key';
17
18
  import { default as createLayoutResizingPlugin } from './pm-plugins/resizing';
@@ -50,7 +51,7 @@ export var selectIntoLayoutSection = function selectIntoLayoutSection(tr) {
50
51
  return tr;
51
52
  };
52
53
  export var layoutPlugin = function layoutPlugin(_ref) {
53
- var _api$analytics2, _api$analytics5;
54
+ var _api$analytics2;
54
55
  var _ref$config = _ref.config,
55
56
  options = _ref$config === void 0 ? {} : _ref$config,
56
57
  api = _ref.api;
@@ -102,6 +103,16 @@ export var layoutPlugin = function layoutPlugin(_ref) {
102
103
  return createLayoutPlugin(options, api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions);
103
104
  }
104
105
  }];
106
+ if (expValEqualsNoExposure('platform_editor_layout_column_menu', 'isEnabled', true)) {
107
+ plugins.push({
108
+ name: 'layoutKeymap',
109
+ plugin: function plugin() {
110
+ return createLayoutKeymapPlugin({
111
+ api: api
112
+ });
113
+ }
114
+ });
115
+ }
105
116
  if ((options.editorAppearance === 'full-page' || options.editorAppearance === 'full-width' || options.editorAppearance === 'max' && editorExperiment('platform_editor_layout_column_resize_handle', true)) && api && editorExperiment('advanced_layouts', true)) {
106
117
  plugins.push({
107
118
  name: 'layoutResizing',
@@ -372,15 +383,19 @@ export var layoutPlugin = function layoutPlugin(_ref) {
372
383
  return pluginKey.getState(editorState);
373
384
  },
374
385
  commands: {
375
- deleteLayoutColumn: deleteLayoutColumn(api === null || api === void 0 || (_api$analytics5 = api.analytics) === null || _api$analytics5 === void 0 ? void 0 : _api$analytics5.actions, api),
386
+ deleteLayoutColumn: function deleteLayoutColumn(inputMethod) {
387
+ var _api$analytics5;
388
+ return _deleteLayoutColumn(api === null || api === void 0 || (_api$analytics5 = api.analytics) === null || _api$analytics5 === void 0 ? void 0 : _api$analytics5.actions, api, inputMethod);
389
+ },
376
390
  distributeLayoutColumns: function distributeLayoutColumns(options) {
377
391
  var _api$analytics6;
378
392
  return _distributeLayoutColumns(api === null || api === void 0 || (_api$analytics6 = api.analytics) === null || _api$analytics6 === void 0 ? void 0 : _api$analytics6.actions, api)(options);
379
393
  },
380
- insertLayoutColumn: function insertLayoutColumn(side) {
394
+ insertLayoutColumn: function insertLayoutColumn(side, inputMethod) {
381
395
  var _api$analytics7;
382
- return _insertLayoutColumn(side, api === null || api === void 0 || (_api$analytics7 = api.analytics) === null || _api$analytics7 === void 0 ? void 0 : _api$analytics7.actions, api);
396
+ return _insertLayoutColumn(side, api === null || api === void 0 || (_api$analytics7 = api.analytics) === null || _api$analytics7 === void 0 ? void 0 : _api$analytics7.actions, api, inputMethod);
383
397
  },
398
+ setLayoutColumnDangerPreview: setLayoutColumnDangerPreview,
384
399
  setLayoutColumnValign: function setLayoutColumnValign(valign) {
385
400
  var _api$analytics8;
386
401
  return _setLayoutColumnValign(valign, api === null || api === void 0 || (_api$analytics8 = api.analytics) === null || _api$analytics8 === void 0 ? void 0 : _api$analytics8.actions, api);
@@ -14,7 +14,7 @@ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
14
14
  import { EVEN_DISTRIBUTED_COL_WIDTHS, MAX_LAYOUT_COLUMNS, MAX_STANDARD_LAYOUT_COLUMNS, MIN_LAYOUT_COLUMN_WIDTH_PERCENT } from './consts';
15
15
  import { pluginKey } from './plugin-key';
16
16
  import { calculateDistribution, isDistributedUniformly, redistributeAfterDeletion, redistributeProportionally } from './utils/layout-column-distribution';
17
- import { getAllLayoutColumnsFromSelection, getSelectedLayoutColumnsFromSelection } from './utils/layout-column-selection';
17
+ import { getAllLayoutColumnsFromSelection, getLayoutColumnsFromContentSelection, getSelectedLayoutColumnsFromSelection } from './utils/layout-column-selection';
18
18
  export var ONE_COL_LAYOUTS = ['single'];
19
19
  export var TWO_COL_LAYOUTS = ['two_equal', 'two_left_sidebar', 'two_right_sidebar'];
20
20
  export var THREE_COL_LAYOUTS = ['three_equal', 'three_with_sidebars'];
@@ -571,12 +571,13 @@ export function getEffectiveMaxLayoutColumns() {
571
571
  return editorExperiment('advanced_layouts', true) ? MAX_LAYOUT_COLUMNS : MAX_STANDARD_LAYOUT_COLUMNS;
572
572
  }
573
573
  var insertLayoutColumnAt = function insertLayoutColumnAt(side, editorAnalyticsAPI) {
574
+ var inputMethod = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : INPUT_METHOD.LAYOUT_COLUMN_MENU;
574
575
  return function (_ref4) {
575
576
  var tr = _ref4.tr;
576
577
  if (!expValEqualsNoExposure('platform_editor_layout_column_menu', 'isEnabled', true)) {
577
578
  return null;
578
579
  }
579
- var selectedLayoutColumnsResult = getSelectedLayoutColumnsFromSelection(tr.selection);
580
+ var selectedLayoutColumnsResult = getLayoutColumnsFromContentSelection(tr.selection);
580
581
  if (!selectedLayoutColumnsResult || selectedLayoutColumnsResult.selectedLayoutColumns.length === 0) {
581
582
  return null;
582
583
  }
@@ -636,7 +637,7 @@ var insertLayoutColumnAt = function insertLayoutColumnAt(side, editorAnalyticsAP
636
637
  attributes: {
637
638
  columnCount: redistributedWidths.length,
638
639
  endIndex: endIndex,
639
- inputMethod: INPUT_METHOD.LAYOUT_COLUMN_MENU,
640
+ inputMethod: inputMethod,
640
641
  selectedCount: selectedColumnCount,
641
642
  side: side,
642
643
  startIndex: startIndex
@@ -648,9 +649,10 @@ var insertLayoutColumnAt = function insertLayoutColumnAt(side, editorAnalyticsAP
648
649
  };
649
650
  };
650
651
  export var insertLayoutColumn = function insertLayoutColumn(side, editorAnalyticsAPI, api) {
652
+ var inputMethod = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : INPUT_METHOD.LAYOUT_COLUMN_MENU;
651
653
  return function (_ref5) {
652
654
  var tr = _ref5.tr;
653
- var result = insertLayoutColumnAt(side, editorAnalyticsAPI)({
655
+ var result = insertLayoutColumnAt(side, editorAnalyticsAPI, inputMethod)({
654
656
  tr: tr
655
657
  });
656
658
  if (result) {
@@ -791,25 +793,43 @@ export var distributeLayoutColumns = function distributeLayoutColumns(editorAnal
791
793
  };
792
794
  export var toggleLayoutColumnMenu = function toggleLayoutColumnMenu(_ref11) {
793
795
  var anchorPos = _ref11.anchorPos,
794
- isOpen = _ref11.isOpen;
796
+ isOpen = _ref11.isOpen,
797
+ openedViaKeyboard = _ref11.openedViaKeyboard;
795
798
  return function (_ref12) {
796
799
  var tr = _ref12.tr;
797
800
  tr.setMeta('toggleLayoutColumnMenu', {
798
801
  anchorPos: anchorPos,
799
- isOpen: isOpen
802
+ isOpen: isOpen,
803
+ openedViaKeyboard: openedViaKeyboard
800
804
  });
801
805
  tr.setMeta('scrollIntoView', false);
802
806
  return tr;
803
807
  };
804
808
  };
805
- export var deleteLayoutColumn = function deleteLayoutColumn(editorAnalyticsAPI, api) {
809
+ export var setLayoutColumnDangerPreview = function setLayoutColumnDangerPreview(show) {
806
810
  return function (_ref13) {
807
- var _api$blockControls4;
811
+ var _selectedLayoutColumn;
808
812
  var tr = _ref13.tr;
813
+ var selectedLayoutColumnsResult = getSelectedLayoutColumnsFromSelection(tr.selection);
814
+ var positions = show ? (_selectedLayoutColumn = selectedLayoutColumnsResult === null || selectedLayoutColumnsResult === void 0 ? void 0 : selectedLayoutColumnsResult.selectedLayoutColumns.map(function (_ref14) {
815
+ var pos = _ref14.pos;
816
+ return pos;
817
+ })) !== null && _selectedLayoutColumn !== void 0 ? _selectedLayoutColumn : [] : null;
818
+ tr.setMeta('layoutColumnDangerPreview', positions);
819
+ tr.setMeta('addToHistory', false);
820
+ tr.setMeta('scrollIntoView', false);
821
+ return tr;
822
+ };
823
+ };
824
+ export var deleteLayoutColumn = function deleteLayoutColumn(editorAnalyticsAPI, api) {
825
+ var inputMethod = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : INPUT_METHOD.LAYOUT_COLUMN_MENU;
826
+ return function (_ref15) {
827
+ var _api$blockControls4;
828
+ var tr = _ref15.tr;
809
829
  if (!expValEqualsNoExposure('platform_editor_layout_column_menu', 'isEnabled', true)) {
810
830
  return null;
811
831
  }
812
- var selectedLayoutColumnsResult = getSelectedLayoutColumnsFromSelection(tr.selection);
832
+ var selectedLayoutColumnsResult = getLayoutColumnsFromContentSelection(tr.selection);
813
833
  if (!selectedLayoutColumnsResult || selectedLayoutColumnsResult.selectedLayoutColumns.length === 0) {
814
834
  return null;
815
835
  }
@@ -826,7 +846,7 @@ export var deleteLayoutColumn = function deleteLayoutColumn(editorAnalyticsAPI,
826
846
  attributes: {
827
847
  columnCount: columnCount,
828
848
  endIndex: endIndex,
829
- inputMethod: INPUT_METHOD.LAYOUT_COLUMN_MENU,
849
+ inputMethod: inputMethod,
830
850
  selectedCount: selectedLayoutColumns.length,
831
851
  startIndex: startIndex
832
852
  },
@@ -0,0 +1,25 @@
1
+ import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
+ import { addColumnAfter, addColumnAfterVO, addColumnBefore, addColumnBeforeVO, bindKeymapWithEditorCommand, deleteColumn, keymap } from '@atlaskit/editor-common/keymaps';
3
+ import { deleteLayoutColumn, insertLayoutColumn } from './actions';
4
+ var bindLayoutColumnShortcut = function bindLayoutColumnShortcut(shortcut, command, list) {
5
+ if (!shortcut) {
6
+ return;
7
+ }
8
+ bindKeymapWithEditorCommand(shortcut, command, list);
9
+ };
10
+
11
+ /**
12
+ * Creates shortcut handlers for layout column actions.
13
+ */
14
+ function keymapPlugin(_ref) {
15
+ var _api$analytics, _api$analytics2, _api$analytics3, _api$analytics4, _api$analytics5;
16
+ var api = _ref.api;
17
+ var list = {};
18
+ bindLayoutColumnShortcut(addColumnBefore.common, insertLayoutColumn('left', api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions, api, INPUT_METHOD.KEYBOARD), list);
19
+ bindLayoutColumnShortcut(addColumnBeforeVO.common, insertLayoutColumn('left', api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions, api, INPUT_METHOD.KEYBOARD), list);
20
+ bindLayoutColumnShortcut(addColumnAfter.common, insertLayoutColumn('right', api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : _api$analytics3.actions, api, INPUT_METHOD.KEYBOARD), list);
21
+ bindLayoutColumnShortcut(addColumnAfterVO.common, insertLayoutColumn('right', api === null || api === void 0 || (_api$analytics4 = api.analytics) === null || _api$analytics4 === void 0 ? void 0 : _api$analytics4.actions, api, INPUT_METHOD.KEYBOARD), list);
22
+ bindLayoutColumnShortcut(deleteColumn.common, deleteLayoutColumn(api === null || api === void 0 || (_api$analytics5 = api.analytics) === null || _api$analytics5 === void 0 ? void 0 : _api$analytics5.actions, api, INPUT_METHOD.KEYBOARD), list);
23
+ return keymap(list);
24
+ }
25
+ export default keymapPlugin;
@@ -52,6 +52,18 @@ var getNodeDecoration = function getNodeDecoration(pos, node) {
52
52
  class: 'selected'
53
53
  })];
54
54
  };
55
+ var getDangerPreviewDecorations = function getDangerPreviewDecorations(state, positions) {
56
+ var _positions$flatMap;
57
+ return (_positions$flatMap = positions === null || positions === void 0 ? void 0 : positions.flatMap(function (pos) {
58
+ var node = state.doc.nodeAt(pos);
59
+ if (!node) {
60
+ return [];
61
+ }
62
+ return [Decoration.node(pos, pos + node.nodeSize, {
63
+ class: 'layout-column-danger-preview'
64
+ })];
65
+ })) !== null && _positions$flatMap !== void 0 ? _positions$flatMap : [];
66
+ };
55
67
  var getInitialPluginState = function getInitialPluginState(options, state) {
56
68
  var maybeLayoutSection = getMaybeLayoutSection(state);
57
69
  var allowBreakout = options.allowBreakout || false;
@@ -67,9 +79,52 @@ var getInitialPluginState = function getInitialPluginState(options, state) {
67
79
  allowSingleColumnLayout: allowSingleColumnLayout,
68
80
  isResizing: false,
69
81
  isLayoutColumnMenuOpen: false,
70
- layoutColumnMenuAnchorPos: undefined
82
+ layoutColumnMenuOpenedViaKeyboard: false,
83
+ layoutColumnMenuAnchorPos: undefined,
84
+ dangerPreviewLayoutColumnPositions: undefined
71
85
  };
72
86
  };
87
+ var reduceLayoutColumnMenuState = function reduceLayoutColumnMenuState(pluginState, action) {
88
+ switch (action.type) {
89
+ case 'toggleLayoutColumnMenu':
90
+ {
91
+ var _action$meta = action.meta,
92
+ anchorPos = _action$meta.anchorPos,
93
+ isOpen = _action$meta.isOpen,
94
+ openedViaKeyboard = _action$meta.openedViaKeyboard;
95
+ var nextIsOpen = isOpen !== null && isOpen !== void 0 ? isOpen : !pluginState.isLayoutColumnMenuOpen;
96
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
97
+ isLayoutColumnMenuOpen: nextIsOpen,
98
+ layoutColumnMenuOpenedViaKeyboard: nextIsOpen ? openedViaKeyboard !== null && openedViaKeyboard !== void 0 ? openedViaKeyboard : false : false,
99
+ layoutColumnMenuAnchorPos: nextIsOpen ? anchorPos : undefined,
100
+ dangerPreviewLayoutColumnPositions: nextIsOpen ? pluginState.dangerPreviewLayoutColumnPositions : undefined
101
+ });
102
+ }
103
+ case 'setDangerPreview':
104
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
105
+ dangerPreviewLayoutColumnPositions: action.positions
106
+ });
107
+ case 'clearDangerPreview':
108
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
109
+ dangerPreviewLayoutColumnPositions: undefined
110
+ });
111
+ case 'setResizeState':
112
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
113
+ isResizing: action.isResizing
114
+ });
115
+ case 'syncSelectionState':
116
+ {
117
+ var maybeLayoutSection = getMaybeLayoutSection(action.state);
118
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
119
+ pos: maybeLayoutSection ? maybeLayoutSection.pos : null,
120
+ selectedLayout: getSelectedLayout(maybeLayoutSection && maybeLayoutSection.node,
121
+ // Ignored via go/ees005
122
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
123
+ pluginState.selectedLayout)
124
+ });
125
+ }
126
+ }
127
+ };
73
128
 
74
129
  // To prevent a single-column layout,
75
130
  // if a user attempts to delete a layout column and
@@ -116,28 +171,39 @@ export default (function (options, editorAnalyticsAPI) {
116
171
  return getInitialPluginState(options, state);
117
172
  },
118
173
  apply: function apply(tr, pluginState, oldState, newState) {
119
- var _columnMenuMeta$isOpe, _tr$getMeta, _pluginKey$getState;
174
+ var _tr$getMeta, _pluginKey$getState;
175
+ var nextPluginState = pluginState;
120
176
  var columnMenuMeta = tr.getMeta('toggleLayoutColumnMenu');
121
- var nextPluginState = columnMenuMeta ? _objectSpread(_objectSpread({}, pluginState), {}, {
122
- isLayoutColumnMenuOpen: (_columnMenuMeta$isOpe = columnMenuMeta.isOpen) !== null && _columnMenuMeta$isOpe !== void 0 ? _columnMenuMeta$isOpe : !pluginState.isLayoutColumnMenuOpen,
123
- layoutColumnMenuAnchorPos: columnMenuMeta.isOpen === false ? undefined : columnMenuMeta.anchorPos
124
- }) : pluginState;
177
+ var dangerPreviewMeta = tr.getMeta('layoutColumnDangerPreview');
178
+ if (columnMenuMeta) {
179
+ nextPluginState = reduceLayoutColumnMenuState(nextPluginState, {
180
+ meta: columnMenuMeta,
181
+ type: 'toggleLayoutColumnMenu'
182
+ });
183
+ }
184
+ if (tr.getMeta('layoutColumnDangerPreview') !== undefined) {
185
+ nextPluginState = reduceLayoutColumnMenuState(nextPluginState, {
186
+ positions: dangerPreviewMeta !== null && dangerPreviewMeta !== void 0 ? dangerPreviewMeta : undefined,
187
+ type: 'setDangerPreview'
188
+ });
189
+ }
190
+ if (tr.docChanged) {
191
+ nextPluginState = reduceLayoutColumnMenuState(nextPluginState, {
192
+ type: 'clearDangerPreview'
193
+ });
194
+ }
125
195
  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;
196
+ nextPluginState = reduceLayoutColumnMenuState(nextPluginState, {
197
+ isResizing: isResizing,
198
+ type: 'setResizeState'
199
+ });
126
200
  if (tr.docChanged || tr.selectionSet) {
127
- var maybeLayoutSection = getMaybeLayoutSection(newState);
128
- var newPluginState = _objectSpread(_objectSpread({}, nextPluginState), {}, {
129
- pos: maybeLayoutSection ? maybeLayoutSection.pos : null,
130
- isResizing: isResizing,
131
- selectedLayout: getSelectedLayout(maybeLayoutSection && maybeLayoutSection.node,
132
- // Ignored via go/ees005
133
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
134
- pluginState.selectedLayout)
201
+ return reduceLayoutColumnMenuState(nextPluginState, {
202
+ state: newState,
203
+ type: 'syncSelectionState'
135
204
  });
136
- return newPluginState;
137
205
  }
138
- return _objectSpread(_objectSpread({}, nextPluginState), {}, {
139
- isResizing: isResizing
140
- });
206
+ return nextPluginState;
141
207
  }
142
208
  },
143
209
  props: {
@@ -147,14 +213,17 @@ export default (function (options, editorAnalyticsAPI) {
147
213
  if (editorExperiment('advanced_layouts', true) && editorExperiment('platform_editor_layout_column_resize_handle', true) && isLayoutResizingPluginAvailable) {
148
214
  var dividerDecorations = getColumnDividerDecorations(state, editorViewRef, editorAnalyticsAPI);
149
215
  var selectedDecorations = layoutState.pos !== null ? getNodeDecoration(layoutState.pos, state.doc.nodeAt(layoutState.pos)) : [];
150
- var allDecorations = [].concat(_toConsumableArray(selectedDecorations), _toConsumableArray(dividerDecorations));
216
+ var _dangerPreviewDecorations = getDangerPreviewDecorations(state, layoutState.dangerPreviewLayoutColumnPositions);
217
+ var allDecorations = [].concat(_toConsumableArray(selectedDecorations), _toConsumableArray(dividerDecorations), _toConsumableArray(_dangerPreviewDecorations));
151
218
  if (allDecorations.length > 0) {
152
219
  return DecorationSet.create(state.doc, allDecorations);
153
220
  }
154
221
  return undefined;
155
222
  }
156
- if (layoutState.pos !== null) {
157
- return DecorationSet.create(state.doc, getNodeDecoration(layoutState.pos, state.doc.nodeAt(layoutState.pos)));
223
+ var dangerPreviewDecorations = getDangerPreviewDecorations(state, layoutState.dangerPreviewLayoutColumnPositions);
224
+ if (layoutState.pos !== null || dangerPreviewDecorations.length > 0) {
225
+ var _selectedDecorations = layoutState.pos !== null ? getNodeDecoration(layoutState.pos, state.doc.nodeAt(layoutState.pos)) : [];
226
+ return DecorationSet.create(state.doc, [].concat(_toConsumableArray(_selectedDecorations), _toConsumableArray(dangerPreviewDecorations)));
158
227
  }
159
228
  return undefined;
160
229
  },
@@ -22,7 +22,7 @@ var findLayoutColumnsFromLayoutSection = function findLayoutColumnsFromLayoutSec
22
22
  };
23
23
  });
24
24
  };
25
- export var getSelectedLayoutColumnsFromSelection = function getSelectedLayoutColumnsFromSelection(selection) {
25
+ var getSelectedLayoutColumns = function getSelectedLayoutColumns(selection, isColumnSelected) {
26
26
  var layoutSection = findLayoutSectionFromSelection(selection);
27
27
  if (!layoutSection) {
28
28
  return undefined;
@@ -35,17 +35,15 @@ export var getSelectedLayoutColumnsFromSelection = function getSelectedLayoutCol
35
35
  }
36
36
  var startIndex = -1;
37
37
  var endIndex = -1;
38
- var selectedLayoutColumns = allLayoutColumns.filter(function (_ref2, index) {
39
- var node = _ref2.node,
40
- pos = _ref2.pos;
41
- var isSelected = selection.from <= pos && selection.to >= pos + node.nodeSize;
42
- if (isSelected) {
38
+ var selectedLayoutColumns = allLayoutColumns.filter(function (column, index) {
39
+ if (isColumnSelected(column, index)) {
43
40
  if (startIndex === -1) {
44
41
  startIndex = index;
45
42
  }
46
43
  endIndex = index;
44
+ return true;
47
45
  }
48
- return isSelected;
46
+ return false;
49
47
  });
50
48
  return {
51
49
  layoutSectionNode: layoutSectionNode,
@@ -55,6 +53,32 @@ export var getSelectedLayoutColumnsFromSelection = function getSelectedLayoutCol
55
53
  endIndex: endIndex
56
54
  };
57
55
  };
56
+ export var getSelectedLayoutColumnsFromSelection = function getSelectedLayoutColumnsFromSelection(selection) {
57
+ return getSelectedLayoutColumns(selection, function (_ref2) {
58
+ var node = _ref2.node,
59
+ pos = _ref2.pos;
60
+ // NodeSelection on a layout column is clearly selected.
61
+ if (selection instanceof NodeSelection && selection.node === node) {
62
+ return true;
63
+ }
64
+
65
+ // For TextSelection, only count columns that are fully contained within the selection
66
+ // (not partial text selections inside a column).
67
+ var nodeEndPos = pos + node.nodeSize;
68
+ return !selection.empty && selection.from <= pos && selection.to >= nodeEndPos;
69
+ });
70
+ };
71
+ export var getLayoutColumnsFromContentSelection = function getLayoutColumnsFromContentSelection(selection) {
72
+ return getSelectedLayoutColumns(selection, function (_ref3) {
73
+ var node = _ref3.node,
74
+ pos = _ref3.pos;
75
+ if (selection instanceof NodeSelection && selection.node === node) {
76
+ return true;
77
+ }
78
+ var nodeEndPos = pos + node.nodeSize;
79
+ return selection.empty ? selection.from > pos && selection.from < nodeEndPos : selection.from < nodeEndPos && selection.to > pos;
80
+ });
81
+ };
58
82
  export var getAllLayoutColumnsFromSelection = function getAllLayoutColumnsFromSelection(selection) {
59
83
  var layoutSection = findLayoutSectionFromSelection(selection);
60
84
  if (!layoutSection) {
@@ -73,8 +97,8 @@ export var getAllLayoutColumnsFromSelection = function getAllLayoutColumnsFromSe
73
97
  };
74
98
  };
75
99
  export var getLayoutColumnValign = function getLayoutColumnValign(layoutColumn) {
76
- var _ref3;
77
- return layoutColumn ? (_ref3 = layoutColumn.attrs.valign) !== null && _ref3 !== void 0 ? _ref3 : 'top' : undefined;
100
+ var _ref4;
101
+ return layoutColumn ? (_ref4 = layoutColumn.attrs.valign) !== null && _ref4 !== void 0 ? _ref4 : 'top' : undefined;
78
102
  };
79
103
  export var getLayoutColumnMenuAnchorPos = function getLayoutColumnMenuAnchorPos(selection, anchorPosFromHandle) {
80
104
  var _clickedSelectedColum, _selectedLayoutColumn;
@@ -82,8 +106,8 @@ export var getLayoutColumnMenuAnchorPos = function getLayoutColumnMenuAnchorPos(
82
106
  if (!selectedLayoutColumns) {
83
107
  return undefined;
84
108
  }
85
- var clickedSelectedColumn = selectedLayoutColumns.selectedLayoutColumns.find(function (_ref4) {
86
- var pos = _ref4.pos;
109
+ var clickedSelectedColumn = selectedLayoutColumns.selectedLayoutColumns.find(function (_ref5) {
110
+ var pos = _ref5.pos;
87
111
  return pos === anchorPosFromHandle;
88
112
  });
89
113
  return (_clickedSelectedColum = clickedSelectedColumn === null || clickedSelectedColumn === void 0 ? void 0 : clickedSelectedColumn.pos) !== null && _clickedSelectedColum !== void 0 ? _clickedSelectedColum : (_selectedLayoutColumn = selectedLayoutColumns.selectedLayoutColumns[0]) === null || _selectedLayoutColumn === void 0 ? void 0 : _selectedLayoutColumn.pos;