@atlaskit/editor-plugin-table 10.4.4 → 10.4.6

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.
@@ -1,9 +1,9 @@
1
- // #region Constants
2
-
3
1
  import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
+ import { isSelectionTableNestedInTable } from '@atlaskit/editor-common/nesting';
3
+ import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
4
4
  import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
5
5
  import { TableMap } from '@atlaskit/editor-tables/table-map';
6
- import { goToNextCell as baseGotoNextCell, findTable } from '@atlaskit/editor-tables/utils';
6
+ import { goToNextCell as baseGotoNextCell, findCellClosestToPos, findTable, findTableClosestToPos, isTableSelected } from '@atlaskit/editor-tables/utils';
7
7
  import { getPluginState } from '../plugin-factory';
8
8
  import { stopKeyboardColumnResizing } from './column-resize';
9
9
  import { insertRowWithAnalytics } from './commands-with-analytics';
@@ -35,7 +35,7 @@ export var goToNextCell = function goToNextCell(editorAnalyticsAPI, ariaNotify,
35
35
  var firstCellPos = map.positionAt(0, 0, table.node) + table.start;
36
36
  var lastCellPos = map.positionAt(map.height - 1, map.width - 1, table.node) + table.start;
37
37
 
38
- // when tabbing backwards at first cell (top left), insert row at the start of table
38
+ // When tabbing backwards at first cell (top left), insert row at the start of table
39
39
  if (firstCellPos === cell.pos && direction === TAB_BACKWARD_DIRECTION) {
40
40
  insertRowWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.KEYBOARD, {
41
41
  index: 0,
@@ -44,7 +44,7 @@ export var goToNextCell = function goToNextCell(editorAnalyticsAPI, ariaNotify,
44
44
  return true;
45
45
  }
46
46
 
47
- // when tabbing forwards at last cell (bottom right), insert row at the end of table
47
+ // When tabbing forwards at last cell (bottom right), insert row at the end of table
48
48
  if (lastCellPos === cell.pos && direction === TAB_FORWARD_DIRECTION) {
49
49
  insertRowWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.KEYBOARD, {
50
50
  index: map.height,
@@ -58,4 +58,94 @@ export var goToNextCell = function goToNextCell(editorAnalyticsAPI, ariaNotify,
58
58
  return true;
59
59
  };
60
60
  };
61
- };
61
+ };
62
+
63
+ /**
64
+ * Moves the cursor vertically from a NodeSelection within a table cell.
65
+ * - If content exists above/below within the cell, lets ProseMirror handle it.
66
+ * - Otherwise, moves to the next cell (down to start, up to last line).
67
+ */
68
+ export var goToNextCellVertical = function goToNextCellVertical(direction) {
69
+ return function (state, dispatch) {
70
+ var selection = state.selection;
71
+ var nestedTableSelection = isSelectionTableNestedInTable(state);
72
+ if (!(selection instanceof NodeSelection) && !nestedTableSelection) {
73
+ return false; // Let ProseMirror handle other selection types
74
+ }
75
+ var table = findTable(selection);
76
+ var cell = findCellClosestToPos(state.doc.resolve(selection.from));
77
+ var selectionPos = selection.from;
78
+
79
+ // Handle when we have nested table fully selected
80
+ if (table && nestedTableSelection && isTableSelected(selection)) {
81
+ var parentTablePos = table.pos;
82
+ table = findTableClosestToPos(state.doc.resolve(parentTablePos));
83
+ cell = findCellClosestToPos(state.doc.resolve(parentTablePos));
84
+ selectionPos = parentTablePos;
85
+ }
86
+ if (!table || !cell || !cell.pos) {
87
+ return false;
88
+ }
89
+ var _state$schema$nodes2 = state.schema.nodes,
90
+ tableCell = _state$schema$nodes2.tableCell,
91
+ tableHeader = _state$schema$nodes2.tableHeader;
92
+
93
+ // Let ProseMirror handle movement within the cell if content exists above/below
94
+ if (cellHasContentInDirection(cell.node, cell.pos, selectionPos, direction)) {
95
+ return false;
96
+ }
97
+
98
+ // Move to the next cell vertically
99
+ var map = TableMap.get(table.node);
100
+ var nextCellPos = map.nextCell(cell.pos - table.start, 'vert', direction);
101
+ if (dispatch && nextCellPos) {
102
+ var _$nextCell$nodeAfter, _$nextCell$nodeAfter2;
103
+ var nextCellStart = table.start + nextCellPos;
104
+ var $nextCell = state.doc.resolve(nextCellStart);
105
+ if ((_$nextCell$nodeAfter = $nextCell.nodeAfter) !== null && _$nextCell$nodeAfter !== void 0 && _$nextCell$nodeAfter.type && [tableCell, tableHeader].includes((_$nextCell$nodeAfter2 = $nextCell.nodeAfter) === null || _$nextCell$nodeAfter2 === void 0 ? void 0 : _$nextCell$nodeAfter2.type)) {
106
+ var contentPos = getTargetPositionInNextCell($nextCell.nodeAfter, nextCellStart, direction);
107
+ dispatch(state.tr.setSelection(TextSelection.create(state.doc, contentPos)));
108
+ return true;
109
+ }
110
+ return false;
111
+ }
112
+ return false; // No next cell found
113
+ };
114
+ };
115
+ function cellHasContentInDirection(cellNode, cellPos, selectionPos, direction) {
116
+ var hasContent = false;
117
+ cellNode.content.forEach(function (node, offset) {
118
+ var nodeStart = cellPos + 1 + offset;
119
+ var nodeEnd = nodeStart + node.nodeSize;
120
+ if (direction === 1 && nodeStart > selectionPos && node.isBlock) {
121
+ hasContent = true; // Content below
122
+ } else if (direction === -1 && nodeEnd <= selectionPos && node.isBlock) {
123
+ hasContent = true; // Content above
124
+ }
125
+ });
126
+ return hasContent;
127
+ }
128
+ function getTargetPositionInNextCell(cellNode, nextCellStart, direction) {
129
+ var contentPos = nextCellStart + 1; // Default: just inside the cell
130
+ if (cellNode.content.size > 0) {
131
+ if (direction === 1) {
132
+ var firstChild = cellNode.firstChild;
133
+ if (firstChild && firstChild.isBlock) {
134
+ contentPos = nextCellStart + 1 + (firstChild.isLeaf ? 0 : 1); // Down: Start of first block
135
+ }
136
+ } else if (direction === -1) {
137
+ var lastBlock;
138
+ var lastOffset = 0;
139
+ cellNode.content.forEach(function (node, offset) {
140
+ if (node.isBlock) {
141
+ lastBlock = node;
142
+ lastOffset = offset;
143
+ }
144
+ });
145
+ if (lastBlock) {
146
+ contentPos = nextCellStart + 1 + lastOffset + (lastBlock.isLeaf ? 0 : lastBlock.nodeSize - 1);
147
+ }
148
+ }
149
+ }
150
+ return contentPos;
151
+ }
@@ -1,5 +1,5 @@
1
1
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
- import { addColumnAfter, addColumnAfterVO, addColumnBefore, addColumnBeforeVO, addRowAfter, addRowAfterVO, addRowBefore, addRowBeforeVO, backspace, bindKeymapWithCommand, decreaseMediaSize, deleteColumn, deleteRow, escape, focusToContextMenuTrigger, increaseMediaSize, moveColumnLeft, moveColumnRight, moveLeft, moveRight, moveRowDown, moveRowUp, nextCell, previousCell, startColumnResizing, toggleTable } from '@atlaskit/editor-common/keymaps';
2
+ import { addColumnAfter, addColumnAfterVO, addColumnBefore, addColumnBeforeVO, addRowAfter, addRowAfterVO, addRowBefore, addRowBeforeVO, backspace, bindKeymapWithCommand, decreaseMediaSize, deleteColumn, deleteRow, escape, focusToContextMenuTrigger, increaseMediaSize, moveColumnLeft, moveColumnRight, moveDown, moveLeft, moveRight, moveRowDown, moveRowUp, moveUp, nextCell, previousCell, startColumnResizing, toggleTable } from '@atlaskit/editor-common/keymaps';
3
3
  import { editorCommandToPMCommand } from '@atlaskit/editor-common/preset';
4
4
  import { chainCommands } from '@atlaskit/editor-prosemirror/commands';
5
5
  import { keymap } from '@atlaskit/editor-prosemirror/keymap';
@@ -8,6 +8,7 @@ import { moveSourceWithAnalyticsViaShortcut } from '../pm-plugins/drag-and-drop/
8
8
  import { goToNextCell, moveCursorBackward, setFocusToCellMenu } from './commands';
9
9
  import { activateNextResizeArea, initiateKeyboardColumnResizing, stopKeyboardColumnResizing } from './commands/column-resize';
10
10
  import { addRowAroundSelection, changeColumnWidthByStepWithAnalytics, deleteSelectedRowsOrColumnsWithAnalyticsViaShortcut, deleteTableIfSelectedWithAnalytics, emptyMultipleCellsWithAnalytics } from './commands/commands-with-analytics';
11
+ import { goToNextCellVertical } from './commands/go-to-next-cell';
11
12
  import { addColumnAfter as addColumnAfterCommand, addColumnBefore as addColumnBeforeCommand, createTable, insertTableWithNestingSupport } from './commands/insert';
12
13
  export function keymapPlugin(getEditorContainerWidth, api, nodeViewPortalProviderAPI, editorAnalyticsAPI, dragAndDropEnabled) {
13
14
  var _pluginInjectionApi$a;
@@ -156,6 +157,14 @@ export function keymapPlugin(getEditorContainerWidth, api, nodeViewPortalProvide
156
157
  bindKeymapWithCommand(
157
158
  // Ignored via go/ees005
158
159
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
160
+ moveDown.common, goToNextCellVertical(1), list);
161
+ bindKeymapWithCommand(
162
+ // Ignored via go/ees005
163
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
164
+ moveUp.common, goToNextCellVertical(-1), list);
165
+ bindKeymapWithCommand(
166
+ // Ignored via go/ees005
167
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
159
168
  decreaseMediaSize.common, changeColumnWidthByStepWithAnalytics(editorAnalyticsAPI, api)(-10, getEditorContainerWidth, isTableScalingEnabled, isTableFixedColumnWidthsOptionEnabled, !!isCommentEditor, INPUT_METHOD.SHORTCUT, ariaNotifyPlugin, getIntl), list);
160
169
  bindKeymapWithCommand(
161
170
  // Ignored via go/ees005
@@ -11,8 +11,10 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
11
11
  import { jsx } from '@emotion/react';
12
12
  import { TableSortOrder as SortOrder } from '@atlaskit/custom-steps';
13
13
  import { CHANGE_ALIGNMENT_REASON, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
14
+ import { DropdownMenuExtensionItems } from '@atlaskit/editor-common/floating-toolbar';
14
15
  import { addColumnAfter, addRowAfter, backspace, tooltip } from '@atlaskit/editor-common/keymaps';
15
16
  import commonMessages, { tableMessages as messages } from '@atlaskit/editor-common/messages';
17
+ import { isSelectionTableNestedInTable } from '@atlaskit/editor-common/nesting';
16
18
  import { getTableContainerWidth } from '@atlaskit/editor-common/node-width';
17
19
  import { cellBackgroundColorPalette, DEFAULT_BORDER_COLOR } from '@atlaskit/editor-common/ui-color';
18
20
  import { closestElement, getChildrenInfo as _getChildrenInfo, getNodeName, isReferencedSource } from '@atlaskit/editor-common/utils';
@@ -23,6 +25,7 @@ import { Rect, TableMap } from '@atlaskit/editor-tables/table-map';
23
25
  import { findCellRectClosestToPos, findTable, getSelectionRect, isSelectionType, splitCell } from '@atlaskit/editor-tables/utils';
24
26
  import AlignImageCenterIcon from '@atlaskit/icon/core/align-image-center';
25
27
  import AlignImageLeftIcon from '@atlaskit/icon/core/align-image-left';
28
+ import CopyIcon from '@atlaskit/icon/core/copy';
26
29
  import CustomizeIcon from '@atlaskit/icon/core/customize';
27
30
  import DeleteIcon from '@atlaskit/icon/core/delete';
28
31
  import TableColumnsDistributeIcon from '@atlaskit/icon/core/table-columns-distribute';
@@ -446,6 +449,7 @@ export var getToolbarConfig = function getToolbarConfig(getEditorContainerWidth,
446
449
  onBlur: clearHoverSelection()
447
450
  }]
448
451
  };
452
+ var isNestedTable = fg('platform_editor_use_nested_table_pm_nodes') && isSelectionTableNestedInTable(state);
449
453
  return {
450
454
  title: 'Table floating controls',
451
455
  getDomRef: getDomRef,
@@ -456,16 +460,59 @@ export var getToolbarConfig = function getToolbarConfig(getEditorContainerWidth,
456
460
  },
457
461
  zIndex: akEditorFloatingPanelZIndex + 1,
458
462
  // Place the context menu slightly above the others
459
- items: [menu, separator(menu.hidden)].concat(_toConsumableArray(alignmentMenu), [separator(alignmentMenu.length === 0)], _toConsumableArray(cellItems), _toConsumableArray(columnSettingsItems), _toConsumableArray(colorPicker), [
460
- // TODO: ED-26961 - editor controls to move to overflow menu
461
- {
463
+ items: [menu, separator(menu.hidden)].concat(_toConsumableArray(alignmentMenu), [separator(alignmentMenu.length === 0)], _toConsumableArray(cellItems), _toConsumableArray(columnSettingsItems), _toConsumableArray(colorPicker), _toConsumableArray(editorExperiment('platform_editor_controls', 'control') ? [{
462
464
  type: 'extensions-placeholder',
463
465
  separator: 'end'
464
- }], _toConsumableArray(editorExperiment('platform_editor_controls', 'control') ? [copyButton, {
466
+ }, copyButton, {
465
467
  type: 'separator'
466
- }, deleteButton] : [deleteButton, {
467
- type: 'separator'
468
- }, copyButton])),
468
+ }, deleteButton] : [{
469
+ type: 'overflow-dropdown',
470
+ dropdownWidth: 220,
471
+ options: [{
472
+ type: 'custom',
473
+ fallback: [],
474
+ render: function render(editorView, dropdownOptions) {
475
+ var _api$extension, _api$extension2, _api$extension3;
476
+ if (!editorView) {
477
+ return null;
478
+ }
479
+ var extensionState = api === null || api === void 0 || (_api$extension = api.extension) === null || _api$extension === void 0 || (_api$extension = _api$extension.sharedState) === null || _api$extension === void 0 ? void 0 : _api$extension.currentState();
480
+ var extensionApi = api === null || api === void 0 || (_api$extension2 = api.extension) === null || _api$extension2 === void 0 ? void 0 : _api$extension2.actions.api();
481
+ if (!extensionApi || !(extensionState !== null && extensionState !== void 0 && extensionState.extensionProvider)) {
482
+ return null;
483
+ }
484
+ return jsx(DropdownMenuExtensionItems, {
485
+ node: tableObject.node,
486
+ editorView: editorView,
487
+ extension: {
488
+ extensionProvider: extensionState !== null && extensionState !== void 0 && extensionState.extensionProvider ? Promise.resolve(extensionState.extensionProvider) : undefined,
489
+ extensionApi: api === null || api === void 0 || (_api$extension3 = api.extension) === null || _api$extension3 === void 0 ? void 0 : _api$extension3.actions.api()
490
+ },
491
+ dropdownOptions: dropdownOptions,
492
+ disabled: function disabled(key) {
493
+ return isNestedTable && ['referentiality:connections', 'chart:insert-chart'].includes(key);
494
+ }
495
+ });
496
+ }
497
+ }, {
498
+ title: intl.formatMessage(commonMessages.copyToClipboard),
499
+ onClick: function onClick() {
500
+ var _api$core, _api$floatingToolbar;
501
+ api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute( // @ts-ignore
502
+ api === null || api === void 0 || (_api$floatingToolbar = api.floatingToolbar) === null || _api$floatingToolbar === void 0 ? void 0 : _api$floatingToolbar.commands.copyNode(nodeType));
503
+ return true;
504
+ },
505
+ icon: jsx(CopyIcon, {
506
+ label: intl.formatMessage(commonMessages.copyToClipboard)
507
+ })
508
+ }, {
509
+ title: intl.formatMessage(commonMessages.delete),
510
+ onClick: deleteTableWithAnalytics(editorAnalyticsAPI),
511
+ icon: jsx(DeleteIcon, {
512
+ label: intl.formatMessage(commonMessages.delete)
513
+ })
514
+ }]
515
+ }])),
469
516
  scrollable: true
470
517
  };
471
518
  }
@@ -3,3 +3,9 @@ import type { EditorAnalyticsAPI } from '@atlaskit/editor-common/analytics';
3
3
  import type { Command } from '@atlaskit/editor-common/types';
4
4
  import type { Direction } from '@atlaskit/editor-tables/types';
5
5
  export declare const goToNextCell: (editorAnalyticsAPI: EditorAnalyticsAPI | undefined | null, ariaNotify?: ((message: string) => void) | undefined, getIntl?: () => IntlShape) => (direction: Direction) => Command;
6
+ /**
7
+ * Moves the cursor vertically from a NodeSelection within a table cell.
8
+ * - If content exists above/below within the cell, lets ProseMirror handle it.
9
+ * - Otherwise, moves to the next cell (down to start, up to last line).
10
+ */
11
+ export declare const goToNextCellVertical: (direction: Direction) => Command;
@@ -5,6 +5,7 @@ import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
5
5
  import type { BatchAttributeUpdatesPlugin } from '@atlaskit/editor-plugin-batch-attribute-updates';
6
6
  import type { ContentInsertionPlugin } from '@atlaskit/editor-plugin-content-insertion';
7
7
  import type { EditorViewModePlugin } from '@atlaskit/editor-plugin-editor-viewmode';
8
+ import type { ExtensionPlugin } from '@atlaskit/editor-plugin-extension';
8
9
  import type { FeatureFlagsPlugin } from '@atlaskit/editor-plugin-feature-flags';
9
10
  import type { GuidelinePlugin } from '@atlaskit/editor-plugin-guideline';
10
11
  import type { SelectionPlugin } from '@atlaskit/editor-plugin-selection';
@@ -45,7 +46,8 @@ export type TablePluginDependencies = [
45
46
  OptionalPlugin<AccessibilityUtilsPlugin>,
46
47
  OptionalPlugin<MediaPlugin>,
47
48
  OptionalPlugin<EditorViewModePlugin>,
48
- OptionalPlugin<FeatureFlagsPlugin>
49
+ OptionalPlugin<FeatureFlagsPlugin>,
50
+ OptionalPlugin<ExtensionPlugin>
49
51
  ];
50
52
  export type TablePlugin = NextEditorPlugin<'table', {
51
53
  pluginConfiguration: TablePluginOptions | undefined;
@@ -3,3 +3,9 @@ import type { EditorAnalyticsAPI } from '@atlaskit/editor-common/analytics';
3
3
  import type { Command } from '@atlaskit/editor-common/types';
4
4
  import type { Direction } from '@atlaskit/editor-tables/types';
5
5
  export declare const goToNextCell: (editorAnalyticsAPI: EditorAnalyticsAPI | undefined | null, ariaNotify?: ((message: string) => void) | undefined, getIntl?: () => IntlShape) => (direction: Direction) => Command;
6
+ /**
7
+ * Moves the cursor vertically from a NodeSelection within a table cell.
8
+ * - If content exists above/below within the cell, lets ProseMirror handle it.
9
+ * - Otherwise, moves to the next cell (down to start, up to last line).
10
+ */
11
+ export declare const goToNextCellVertical: (direction: Direction) => Command;
@@ -5,6 +5,7 @@ import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
5
5
  import type { BatchAttributeUpdatesPlugin } from '@atlaskit/editor-plugin-batch-attribute-updates';
6
6
  import type { ContentInsertionPlugin } from '@atlaskit/editor-plugin-content-insertion';
7
7
  import type { EditorViewModePlugin } from '@atlaskit/editor-plugin-editor-viewmode';
8
+ import type { ExtensionPlugin } from '@atlaskit/editor-plugin-extension';
8
9
  import type { FeatureFlagsPlugin } from '@atlaskit/editor-plugin-feature-flags';
9
10
  import type { GuidelinePlugin } from '@atlaskit/editor-plugin-guideline';
10
11
  import type { SelectionPlugin } from '@atlaskit/editor-plugin-selection';
@@ -45,7 +46,8 @@ export type TablePluginDependencies = [
45
46
  OptionalPlugin<AccessibilityUtilsPlugin>,
46
47
  OptionalPlugin<MediaPlugin>,
47
48
  OptionalPlugin<EditorViewModePlugin>,
48
- OptionalPlugin<FeatureFlagsPlugin>
49
+ OptionalPlugin<FeatureFlagsPlugin>,
50
+ OptionalPlugin<ExtensionPlugin>
49
51
  ];
50
52
  export type TablePlugin = NextEditorPlugin<'table', {
51
53
  pluginConfiguration: TablePluginOptions | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-table",
3
- "version": "10.4.4",
3
+ "version": "10.4.6",
4
4
  "description": "Table plugin for the @atlaskit/editor",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -32,13 +32,14 @@
32
32
  "@atlaskit/adf-schema": "^47.6.0",
33
33
  "@atlaskit/button": "^21.1.0",
34
34
  "@atlaskit/custom-steps": "^0.11.0",
35
- "@atlaskit/editor-common": "^102.10.0",
35
+ "@atlaskit/editor-common": "^102.11.0",
36
36
  "@atlaskit/editor-palette": "^2.1.0",
37
37
  "@atlaskit/editor-plugin-accessibility-utils": "^2.0.0",
38
38
  "@atlaskit/editor-plugin-analytics": "^2.2.0",
39
39
  "@atlaskit/editor-plugin-batch-attribute-updates": "^2.1.0",
40
40
  "@atlaskit/editor-plugin-content-insertion": "^2.1.0",
41
41
  "@atlaskit/editor-plugin-editor-viewmode": "^3.0.0",
42
+ "@atlaskit/editor-plugin-extension": "5.0.7",
42
43
  "@atlaskit/editor-plugin-guideline": "^2.0.0",
43
44
  "@atlaskit/editor-plugin-selection": "^2.1.0",
44
45
  "@atlaskit/editor-plugin-width": "^3.0.0",
@@ -51,7 +52,7 @@
51
52
  "@atlaskit/pragmatic-drag-and-drop": "^1.5.0",
52
53
  "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.0",
53
54
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.0",
54
- "@atlaskit/primitives": "^14.1.0",
55
+ "@atlaskit/primitives": "^14.2.0",
55
56
  "@atlaskit/theme": "^18.0.0",
56
57
  "@atlaskit/tmp-editor-statsig": "^4.1.0",
57
58
  "@atlaskit/toggle": "^15.0.0",
@@ -152,9 +153,6 @@
152
153
  "platform_editor_nested_tables_paste_wrap_fix": {
153
154
  "type": "boolean"
154
155
  },
155
- "platform_editor_advanced_layouts_post_fix_patch_4": {
156
- "type": "boolean"
157
- },
158
156
  "nested_table_control_padding_with_css": {
159
157
  "type": "boolean"
160
158
  },
@@ -454,8 +454,7 @@ export default class TableRow extends TableNodeView<HTMLTableRowElement> impleme
454
454
  if (
455
455
  // is Safari
456
456
  navigator.userAgent.includes('AppleWebKit') &&
457
- !navigator.userAgent.includes('Chrome') &&
458
- fg('platform_editor_advanced_layouts_post_fix_patch_4')
457
+ !navigator.userAgent.includes('Chrome')
459
458
  ) {
460
459
  const pos = this.getPos();
461
460
  if (typeof pos === 'number') {
@@ -150,8 +150,8 @@ export class TableStickyScrollbar {
150
150
  // so the boundingClientRect.top will never be less than the rootBounds.top,
151
151
  // so we need to check if the boundingClientRect.top is less than 20% of the rootBounds.height
152
152
  // to determine if the bottom sentinel is above the scroll area
153
- (entry.boundingClientRect.top < ((entry.rootBounds?.height || 0) * 0.2) &&
154
- fg('platform_editor_scroll_table_flickering_fix'));
153
+ (entry.boundingClientRect.top < (entry.rootBounds?.height || 0) * 0.2 &&
154
+ fg('platform_editor_scroll_table_flickering_fix'));
155
155
 
156
156
  this.bottomSentinelState = sentinelIsAboveScrollArea
157
157
  ? 'above'
@@ -166,7 +166,6 @@ export class TableStickyScrollbar {
166
166
  const sentinelIsBelowScrollArea =
167
167
  (entry.rootBounds?.bottom || 0) < entry.boundingClientRect.top;
168
168
 
169
-
170
169
  this.topSentinelState = sentinelIsBelowScrollArea
171
170
  ? 'below'
172
171
  : entry.isIntersecting
@@ -1,13 +1,21 @@
1
- // #region Constants
2
1
  import type { IntlShape } from 'react-intl-next/src/types';
3
2
 
4
3
  import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
5
4
  import type { EditorAnalyticsAPI } from '@atlaskit/editor-common/analytics';
5
+ import { isSelectionTableNestedInTable } from '@atlaskit/editor-common/nesting';
6
6
  import type { Command } from '@atlaskit/editor-common/types';
7
+ import { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
8
+ import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
7
9
  import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
8
10
  import { TableMap } from '@atlaskit/editor-tables/table-map';
9
11
  import type { Direction } from '@atlaskit/editor-tables/types';
10
- import { goToNextCell as baseGotoNextCell, findTable } from '@atlaskit/editor-tables/utils';
12
+ import {
13
+ goToNextCell as baseGotoNextCell,
14
+ findCellClosestToPos,
15
+ findTable,
16
+ findTableClosestToPos,
17
+ isTableSelected,
18
+ } from '@atlaskit/editor-tables/utils';
11
19
 
12
20
  import { getPluginState } from '../plugin-factory';
13
21
 
@@ -47,7 +55,7 @@ export const goToNextCell =
47
55
  const firstCellPos = map.positionAt(0, 0, table.node) + table.start;
48
56
  const lastCellPos = map.positionAt(map.height - 1, map.width - 1, table.node) + table.start;
49
57
 
50
- // when tabbing backwards at first cell (top left), insert row at the start of table
58
+ // When tabbing backwards at first cell (top left), insert row at the start of table
51
59
  if (firstCellPos === cell.pos && direction === TAB_BACKWARD_DIRECTION) {
52
60
  insertRowWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.KEYBOARD, {
53
61
  index: 0,
@@ -56,7 +64,7 @@ export const goToNextCell =
56
64
  return true;
57
65
  }
58
66
 
59
- // when tabbing forwards at last cell (bottom right), insert row at the end of table
67
+ // When tabbing forwards at last cell (bottom right), insert row at the end of table
60
68
  if (lastCellPos === cell.pos && direction === TAB_FORWARD_DIRECTION) {
61
69
  insertRowWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.KEYBOARD, {
62
70
  index: map.height,
@@ -71,3 +79,120 @@ export const goToNextCell =
71
79
 
72
80
  return true;
73
81
  };
82
+
83
+ /**
84
+ * Moves the cursor vertically from a NodeSelection within a table cell.
85
+ * - If content exists above/below within the cell, lets ProseMirror handle it.
86
+ * - Otherwise, moves to the next cell (down to start, up to last line).
87
+ */
88
+ export const goToNextCellVertical =
89
+ (direction: Direction): Command =>
90
+ (state, dispatch) => {
91
+ const { selection } = state;
92
+ const nestedTableSelection = isSelectionTableNestedInTable(state);
93
+
94
+ if (!(selection instanceof NodeSelection) && !nestedTableSelection) {
95
+ return false; // Let ProseMirror handle other selection types
96
+ }
97
+
98
+ let table = findTable(selection);
99
+ let cell = findCellClosestToPos(state.doc.resolve(selection.from));
100
+ let selectionPos = selection.from;
101
+
102
+ // Handle when we have nested table fully selected
103
+ if (table && nestedTableSelection && isTableSelected(selection)) {
104
+ const parentTablePos = table.pos;
105
+ table = findTableClosestToPos(state.doc.resolve(parentTablePos));
106
+ cell = findCellClosestToPos(state.doc.resolve(parentTablePos));
107
+ selectionPos = parentTablePos;
108
+ }
109
+
110
+ if (!table || !cell || !cell.pos) {
111
+ return false;
112
+ }
113
+
114
+ const { tableCell, tableHeader } = state.schema.nodes;
115
+
116
+ // Let ProseMirror handle movement within the cell if content exists above/below
117
+ if (cellHasContentInDirection(cell.node, cell.pos, selectionPos, direction)) {
118
+ return false;
119
+ }
120
+
121
+ // Move to the next cell vertically
122
+ const map = TableMap.get(table.node);
123
+ const nextCellPos = map.nextCell(cell.pos - table.start, 'vert', direction);
124
+ if (dispatch && nextCellPos) {
125
+ const nextCellStart = table.start + nextCellPos;
126
+ const $nextCell = state.doc.resolve(nextCellStart);
127
+
128
+ if (
129
+ $nextCell.nodeAfter?.type &&
130
+ [tableCell, tableHeader].includes($nextCell.nodeAfter?.type)
131
+ ) {
132
+ const contentPos = getTargetPositionInNextCell(
133
+ $nextCell.nodeAfter,
134
+ nextCellStart,
135
+ direction,
136
+ );
137
+
138
+ dispatch(state.tr.setSelection(TextSelection.create(state.doc, contentPos)));
139
+ return true;
140
+ }
141
+
142
+ return false;
143
+ }
144
+
145
+ return false; // No next cell found
146
+ };
147
+
148
+ function cellHasContentInDirection(
149
+ cellNode: PMNode,
150
+ cellPos: number,
151
+ selectionPos: number,
152
+ direction: Direction,
153
+ ): boolean {
154
+ let hasContent = false;
155
+
156
+ cellNode.content.forEach((node: PMNode, offset: number) => {
157
+ const nodeStart = cellPos + 1 + offset;
158
+ const nodeEnd = nodeStart + node.nodeSize;
159
+
160
+ if (direction === 1 && nodeStart > selectionPos && node.isBlock) {
161
+ hasContent = true; // Content below
162
+ } else if (direction === -1 && nodeEnd <= selectionPos && node.isBlock) {
163
+ hasContent = true; // Content above
164
+ }
165
+ });
166
+
167
+ return hasContent;
168
+ }
169
+
170
+ function getTargetPositionInNextCell(
171
+ cellNode: PMNode,
172
+ nextCellStart: number,
173
+ direction: Direction,
174
+ ): number {
175
+ let contentPos = nextCellStart + 1; // Default: just inside the cell
176
+ if (cellNode.content.size > 0) {
177
+ if (direction === 1) {
178
+ const firstChild = cellNode.firstChild;
179
+ if (firstChild && firstChild.isBlock) {
180
+ contentPos = nextCellStart + 1 + (firstChild.isLeaf ? 0 : 1); // Down: Start of first block
181
+ }
182
+ } else if (direction === -1) {
183
+ let lastBlock: PMNode | undefined;
184
+ let lastOffset = 0;
185
+ cellNode.content.forEach((node: PMNode, offset: number) => {
186
+ if (node.isBlock) {
187
+ lastBlock = node;
188
+ lastOffset = offset;
189
+ }
190
+ });
191
+ if (lastBlock) {
192
+ contentPos =
193
+ nextCellStart + 1 + lastOffset + (lastBlock.isLeaf ? 0 : lastBlock.nodeSize - 1);
194
+ }
195
+ }
196
+ }
197
+ return contentPos;
198
+ }
@@ -27,10 +27,12 @@ import {
27
27
  increaseMediaSize,
28
28
  moveColumnLeft,
29
29
  moveColumnRight,
30
+ moveDown,
30
31
  moveLeft,
31
32
  moveRight,
32
33
  moveRowDown,
33
34
  moveRowUp,
35
+ moveUp,
34
36
  nextCell,
35
37
  previousCell,
36
38
  startColumnResizing,
@@ -60,6 +62,7 @@ import {
60
62
  deleteTableIfSelectedWithAnalytics,
61
63
  emptyMultipleCellsWithAnalytics,
62
64
  } from './commands/commands-with-analytics';
65
+ import { goToNextCellVertical } from './commands/go-to-next-cell';
63
66
  import {
64
67
  addColumnAfter as addColumnAfterCommand,
65
68
  addColumnBefore as addColumnBeforeCommand,
@@ -362,6 +365,22 @@ export function keymapPlugin(
362
365
  list,
363
366
  );
364
367
 
368
+ bindKeymapWithCommand(
369
+ // Ignored via go/ees005
370
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
371
+ moveDown.common!,
372
+ goToNextCellVertical(1),
373
+ list,
374
+ );
375
+
376
+ bindKeymapWithCommand(
377
+ // Ignored via go/ees005
378
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
379
+ moveUp.common!,
380
+ goToNextCellVertical(-1),
381
+ list,
382
+ );
383
+
365
384
  bindKeymapWithCommand(
366
385
  // Ignored via go/ees005
367
386
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -11,6 +11,7 @@ import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
11
11
  import type { BatchAttributeUpdatesPlugin } from '@atlaskit/editor-plugin-batch-attribute-updates';
12
12
  import type { ContentInsertionPlugin } from '@atlaskit/editor-plugin-content-insertion';
13
13
  import type { EditorViewModePlugin } from '@atlaskit/editor-plugin-editor-viewmode';
14
+ import type { ExtensionPlugin } from '@atlaskit/editor-plugin-extension';
14
15
  import type { FeatureFlagsPlugin } from '@atlaskit/editor-plugin-feature-flags';
15
16
  import type { GuidelinePlugin } from '@atlaskit/editor-plugin-guideline';
16
17
  import type { SelectionPlugin } from '@atlaskit/editor-plugin-selection';
@@ -80,6 +81,7 @@ export type TablePluginDependencies = [
80
81
  OptionalPlugin<MediaPlugin>,
81
82
  OptionalPlugin<EditorViewModePlugin>,
82
83
  OptionalPlugin<FeatureFlagsPlugin>,
84
+ OptionalPlugin<ExtensionPlugin>,
83
85
  ];
84
86
 
85
87
  export type TablePlugin = NextEditorPlugin<