@atlaskit/editor-plugin-table 7.18.2 → 7.18.3

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 (47) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cjs/commands/index.js +6 -0
  3. package/dist/cjs/commands/misc.js +15 -1
  4. package/dist/cjs/plugin.js +7 -4
  5. package/dist/cjs/pm-plugins/keymap.js +3 -0
  6. package/dist/cjs/reducer.js +1 -0
  7. package/dist/cjs/ui/FloatingContextualButton/index.js +18 -2
  8. package/dist/cjs/ui/FloatingContextualMenu/ContextualMenu.js +170 -35
  9. package/dist/cjs/ui/FloatingContextualMenu/index.js +4 -2
  10. package/dist/es2019/commands/index.js +1 -1
  11. package/dist/es2019/commands/misc.js +9 -1
  12. package/dist/es2019/plugin.js +7 -4
  13. package/dist/es2019/pm-plugins/keymap.js +5 -2
  14. package/dist/es2019/reducer.js +1 -0
  15. package/dist/es2019/ui/FloatingContextualButton/index.js +17 -2
  16. package/dist/es2019/ui/FloatingContextualMenu/ContextualMenu.js +159 -24
  17. package/dist/es2019/ui/FloatingContextualMenu/index.js +4 -2
  18. package/dist/esm/commands/index.js +1 -1
  19. package/dist/esm/commands/misc.js +14 -0
  20. package/dist/esm/plugin.js +7 -4
  21. package/dist/esm/pm-plugins/keymap.js +5 -2
  22. package/dist/esm/reducer.js +1 -0
  23. package/dist/esm/ui/FloatingContextualButton/index.js +15 -2
  24. package/dist/esm/ui/FloatingContextualMenu/ContextualMenu.js +171 -40
  25. package/dist/esm/ui/FloatingContextualMenu/index.js +4 -2
  26. package/dist/types/commands/index.d.ts +1 -1
  27. package/dist/types/commands/misc.d.ts +1 -0
  28. package/dist/types/types.d.ts +6 -0
  29. package/dist/types/ui/FloatingContextualButton/index.d.ts +1 -0
  30. package/dist/types/ui/FloatingContextualMenu/ContextualMenu.d.ts +7 -3
  31. package/dist/types/ui/FloatingContextualMenu/index.d.ts +2 -1
  32. package/dist/types-ts4.5/commands/index.d.ts +1 -1
  33. package/dist/types-ts4.5/commands/misc.d.ts +1 -0
  34. package/dist/types-ts4.5/types.d.ts +6 -0
  35. package/dist/types-ts4.5/ui/FloatingContextualButton/index.d.ts +1 -0
  36. package/dist/types-ts4.5/ui/FloatingContextualMenu/ContextualMenu.d.ts +7 -3
  37. package/dist/types-ts4.5/ui/FloatingContextualMenu/index.d.ts +2 -1
  38. package/package.json +4 -1
  39. package/src/commands/index.ts +1 -0
  40. package/src/commands/misc.ts +13 -0
  41. package/src/plugin.tsx +6 -1
  42. package/src/pm-plugins/keymap.ts +6 -1
  43. package/src/reducer.ts +1 -0
  44. package/src/types.ts +27 -20
  45. package/src/ui/FloatingContextualButton/index.tsx +19 -1
  46. package/src/ui/FloatingContextualMenu/ContextualMenu.tsx +209 -30
  47. package/src/ui/FloatingContextualMenu/index.tsx +3 -0
@@ -17,6 +17,7 @@ import {
17
17
  deleteColumn,
18
18
  deleteRow,
19
19
  escape,
20
+ focusToContextMenuTrigger,
20
21
  increaseMediaSize,
21
22
  moveColumnLeft,
22
23
  moveColumnRight,
@@ -35,7 +36,7 @@ import { chainCommands } from '@atlaskit/editor-prosemirror/commands';
35
36
  import { keymap } from '@atlaskit/editor-prosemirror/keymap';
36
37
  import { getBooleanFF } from '@atlaskit/platform-feature-flags';
37
38
 
38
- import { goToNextCell, moveCursorBackward } from '../commands';
39
+ import { goToNextCell, moveCursorBackward, setFocusToCellMenu } from '../commands';
39
40
  import {
40
41
  addRowAroundSelection,
41
42
  changeColumnWidthByStepWithAnalytics,
@@ -298,6 +299,10 @@ export function keymapPlugin(
298
299
  );
299
300
  }
300
301
 
302
+ if (getBooleanFF('platform.editor.a11y-table-context-menu_y4c9c')) {
303
+ bindKeymapWithCommand(focusToContextMenuTrigger.common!, setFocusToCellMenu(), list);
304
+ }
305
+
301
306
  return keymap(list) as SafePlugin;
302
307
  }
303
308
 
package/src/reducer.ts CHANGED
@@ -135,6 +135,7 @@ export default (pluginState: TablePluginState, action: TablePluginAction): Table
135
135
  case 'HOVER_CELL':
136
136
  case 'SHOW_RESIZE_HANDLE_LINE':
137
137
  case 'SET_EDITOR_FOCUS':
138
+ case 'SET_CELL_MENU_OPEN':
138
139
  return { ...pluginState, ...action.data };
139
140
 
140
141
  default:
package/src/types.ts CHANGED
@@ -178,6 +178,7 @@ export interface TablePluginState {
178
178
  // use options.isTableScalingEnabled and avoid using pluginState.isTableScalingEnabled or
179
179
  // const { isTableScalingEnabled } = getPluginState(state) for that purpose.
180
180
  isTableScalingEnabled?: boolean;
181
+ isCellMenuOpenByKeyboard?: boolean;
181
182
  }
182
183
 
183
184
  export type TablePluginAction =
@@ -194,7 +195,7 @@ export type TablePluginAction =
194
195
  isHeaderRowEnabled: boolean;
195
196
  isHeaderColumnEnabled: boolean;
196
197
  };
197
- }
198
+ }
198
199
  | {
199
200
  type: 'HOVER_ROWS';
200
201
  data: {
@@ -202,13 +203,13 @@ export type TablePluginAction =
202
203
  hoveredRows: number[];
203
204
  isInDanger?: boolean;
204
205
  };
205
- }
206
+ }
206
207
  | {
207
208
  type: 'HOVER_MERGED_CELLS';
208
209
  data: {
209
210
  decorationSet: DecorationSet;
210
211
  };
211
- }
212
+ }
212
213
  | {
213
214
  type: 'HOVER_COLUMNS';
214
215
  data: {
@@ -216,7 +217,7 @@ export type TablePluginAction =
216
217
  hoveredColumns: number[];
217
218
  isInDanger?: boolean;
218
219
  };
219
- }
220
+ }
220
221
  | {
221
222
  type: 'HOVER_TABLE';
222
223
  data: {
@@ -225,7 +226,7 @@ export type TablePluginAction =
225
226
  hoveredColumns: number[];
226
227
  isInDanger?: boolean;
227
228
  };
228
- }
229
+ }
229
230
  | {
230
231
  type: 'START_KEYBOARD_COLUMN_RESIZE';
231
232
  data: {
@@ -235,7 +236,7 @@ export type TablePluginAction =
235
236
  resizeHandleIncludeTooltip: boolean;
236
237
  isKeyboardResize?: boolean;
237
238
  };
238
- }
239
+ }
239
240
  | {
240
241
  type: 'ADD_RESIZE_HANDLE_DECORATIONS';
241
242
  data: {
@@ -245,7 +246,7 @@ export type TablePluginAction =
245
246
  resizeHandleIncludeTooltip: boolean;
246
247
  isKeyboardResize?: boolean;
247
248
  };
248
- }
249
+ }
249
250
  | {
250
251
  type: 'UPDATE_RESIZE_HANDLE_DECORATIONS';
251
252
  data: {
@@ -254,21 +255,21 @@ export type TablePluginAction =
254
255
  resizeHandleColumnIndex: number | undefined;
255
256
  resizeHandleIncludeTooltip: boolean | undefined;
256
257
  };
257
- }
258
+ }
258
259
  | {
259
260
  type: 'UPDATE_TABLE_WIDTH_TO_WIDEST';
260
261
  data: {
261
262
  widthToWidest: WidthToWidest | undefined;
262
263
  };
263
- }
264
+ }
264
265
  | {
265
266
  type: 'REMOVE_RESIZE_HANDLE_DECORATIONS';
266
267
  data: { decorationSet: DecorationSet };
267
- }
268
+ }
268
269
  | {
269
270
  type: 'STOP_KEYBOARD_COLUMN_RESIZE';
270
271
  data: { decorationSet: DecorationSet };
271
- }
272
+ }
272
273
  | { type: 'CLEAR_HOVER_SELECTION'; data: { decorationSet: DecorationSet } }
273
274
  | { type: 'SHOW_RESIZE_HANDLE_LINE'; data: { decorationSet: DecorationSet } }
274
275
  | { type: 'HIDE_RESIZE_HANDLE_LINE'; data: { decorationSet: DecorationSet } }
@@ -277,42 +278,48 @@ export type TablePluginAction =
277
278
  data: {
278
279
  hoveredCell: CellHoverMeta;
279
280
  };
280
- }
281
+ }
281
282
  | {
282
283
  type: 'TABLE_HOVERED';
283
284
  data: {
284
285
  isTableHovered: boolean;
285
286
  };
286
- }
287
+ }
287
288
  | { type: 'SET_TARGET_CELL_POSITION'; data: { targetCellPosition?: number } }
288
289
  | {
289
290
  type: 'SELECT_COLUMN';
290
291
  data: { targetCellPosition: number; decorationSet: DecorationSet };
291
- }
292
+ }
292
293
  | { type: 'SHOW_INSERT_ROW_BUTTON'; data: { insertRowButtonIndex: number } }
293
294
  | {
294
295
  type: 'SHOW_INSERT_COLUMN_BUTTON';
295
296
  data: { insertColumnButtonIndex: number };
296
- }
297
+ }
297
298
  | {
298
299
  type: 'HIDE_INSERT_COLUMN_OR_ROW_BUTTON';
299
- }
300
- | { type: 'TOGGLE_CONTEXTUAL_MENU' };
300
+ }
301
+ | { type: 'TOGGLE_CONTEXTUAL_MENU' }
302
+ | {
303
+ type: 'SET_CELL_MENU_OPEN';
304
+ data: {
305
+ isCellMenuOpenByKeyboard: boolean;
306
+ };
307
+ };
301
308
 
302
309
  export type ColumnResizingPluginAction =
303
310
  | {
304
311
  type: 'SET_RESIZE_HANDLE_POSITION';
305
312
  data: { resizeHandlePos: number | null };
306
- }
313
+ }
307
314
  | { type: 'STOP_RESIZING' }
308
315
  | {
309
316
  type: 'SET_DRAGGING';
310
317
  data: { dragging: { startX: number; startWidth: number } | null };
311
- }
318
+ }
312
319
  | {
313
320
  type: 'SET_LAST_CLICK';
314
321
  data: { lastClick: { x: number; y: number; time: number } | null };
315
- };
322
+ };
316
323
 
317
324
  export enum TableDecorations {
318
325
  /** Classic controls */
@@ -1,5 +1,5 @@
1
1
  /** @jsx jsx */
2
- import React from 'react';
2
+ import React, { useEffect } from 'react';
3
3
 
4
4
  // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
5
5
  import { jsx } from '@emotion/react';
@@ -17,6 +17,7 @@ import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils';
17
17
  import type { EditorView } from '@atlaskit/editor-prosemirror/view';
18
18
  import { akEditorSmallZIndex } from '@atlaskit/editor-shared-styles';
19
19
  import ExpandIcon from '@atlaskit/icon/glyph/chevron-down';
20
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
20
21
 
21
22
  import { toggleContextualMenu } from '../../commands';
22
23
  import type { RowStickyState } from '../../pm-plugins/sticky-headers';
@@ -37,6 +38,7 @@ export interface Props {
37
38
  isNumberColumnEnabled?: boolean;
38
39
  stickyHeader?: RowStickyState;
39
40
  dispatchAnalyticsEvent?: DispatchAnalyticsEvent;
41
+ isCellMenuOpenByKeyboard?: boolean;
40
42
  }
41
43
 
42
44
  const BUTTON_OFFSET = 3;
@@ -50,6 +52,7 @@ const FloatingContextualButtonInner = React.memo((props: Props & WrappedComponen
50
52
  stickyHeader,
51
53
  tableWrapper,
52
54
  targetCellPosition,
55
+ isCellMenuOpenByKeyboard,
53
56
  intl: { formatMessage },
54
57
  } = props; // : Props & WrappedComponentProps
55
58
 
@@ -69,6 +72,16 @@ const FloatingContextualButtonInner = React.memo((props: Props & WrappedComponen
69
72
  let targetCellRef: Node | undefined;
70
73
  targetCellRef = findDomRefAtPos(targetCellPosition, domAtPos);
71
74
 
75
+ useEffect(() => {
76
+ if (getBooleanFF('platform.editor.a11y-table-context-menu_y4c9c')) {
77
+ if (isCellMenuOpenByKeyboard && !isContextualMenuOpen) {
78
+ const { state, dispatch } = editorView;
79
+ // open the menu when the keyboard shortcut is pressed
80
+ toggleContextualMenu()(state, dispatch);
81
+ }
82
+ }
83
+ }, [isCellMenuOpenByKeyboard, isContextualMenuOpen, editorView]);
84
+
72
85
  if (!targetCellRef || !(targetCellRef instanceof HTMLElement)) {
73
86
  return null;
74
87
  }
@@ -92,6 +105,11 @@ const FloatingContextualButtonInner = React.memo((props: Props & WrappedComponen
92
105
  onClick={handleClick}
93
106
  iconBefore={<ExpandIcon label="" />}
94
107
  aria-label={labelCellOptions}
108
+ aria-expanded={
109
+ getBooleanFF('platform.editor.a11y-table-context-menu_y4c9c')
110
+ ? isContextualMenuOpen
111
+ : undefined
112
+ }
95
113
  />
96
114
  </div>
97
115
  );
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable @atlaskit/design-system/prefer-primitives */
2
2
  /** @jsx jsx */
3
- import { Component } from 'react';
3
+ import React, { Component } from 'react';
4
+ import type { PointerEvent } from 'react';
4
5
 
5
6
  // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
6
7
  import { jsx } from '@emotion/react';
@@ -24,9 +25,14 @@ import {
24
25
  backgroundPaletteTooltipMessages,
25
26
  cellBackgroundColorPalette,
26
27
  ColorPalette,
28
+ getSelectedRowAndColumnFromPalette,
27
29
  } from '@atlaskit/editor-common/ui-color';
28
30
  import type { MenuItem } from '@atlaskit/editor-common/ui-menu';
29
- import { ArrowKeyNavigationType, DropdownMenu } from '@atlaskit/editor-common/ui-menu';
31
+ import {
32
+ ArrowKeyNavigationProvider,
33
+ ArrowKeyNavigationType,
34
+ DropdownMenu,
35
+ } from '@atlaskit/editor-common/ui-menu';
30
36
  import { closestElement } from '@atlaskit/editor-common/utils';
31
37
  import { hexToEditorBackgroundPaletteColor } from '@atlaskit/editor-palette';
32
38
  import type { EditorView } from '@atlaskit/editor-prosemirror/view';
@@ -43,6 +49,7 @@ import {
43
49
  hoverColumns,
44
50
  hoverMergedCells,
45
51
  hoverRows,
52
+ setFocusToCellMenu,
46
53
  toggleContextualMenu,
47
54
  } from '../../commands';
48
55
  import {
@@ -58,6 +65,7 @@ import {
58
65
  splitCellWithAnalytics,
59
66
  } from '../../commands-with-analytics';
60
67
  import { getPluginState } from '../../pm-plugins/plugin-factory';
68
+ import { pluginKey as tablePluginKey } from '../../pm-plugins/plugin-key';
61
69
  import { getNewResizeStateFromSelectedColumns } from '../../pm-plugins/table-resizing/utils/resize-state';
62
70
  import { canMergeCells } from '../../transforms';
63
71
  import { TableCssClassName as ClassName } from '../../types';
@@ -66,7 +74,11 @@ import {
66
74
  getSelectedColumnIndexes,
67
75
  getSelectedRowIndexes,
68
76
  } from '../../utils';
69
- import { contextualMenuDropdownWidth, contextualMenuDropdownWidthDnD } from '../consts';
77
+ import {
78
+ colorPalletteColumns,
79
+ contextualMenuDropdownWidth,
80
+ contextualMenuDropdownWidthDnD,
81
+ } from '../consts';
70
82
  import { AddColRightIcon, AddRowBelowIcon, MergeCellsIcon, SplitCellIcon } from '../icons';
71
83
 
72
84
  import { cellColourPreviewStyles, elementBeforeIconStyles } from './styles';
@@ -85,40 +97,78 @@ export interface Props {
85
97
  editorAnalyticsAPI?: EditorAnalyticsAPI;
86
98
  getEditorContainerWidth: GetEditorContainerWidth;
87
99
  getEditorFeatureFlags?: GetEditorFeatureFlags;
100
+ isCellMenuOpenByKeyboard?: boolean;
88
101
  }
89
102
 
90
103
  export interface State {
91
104
  isSubmenuOpen: boolean;
105
+ isOpenAllowed: boolean;
92
106
  }
93
-
107
+ const arrowsList = new Set(['ArrowRight', 'ArrowLeft']);
94
108
  export class ContextualMenu extends Component<Props & WrappedComponentProps, State> {
95
109
  state: State = {
96
110
  isSubmenuOpen: false,
111
+ isOpenAllowed: false,
97
112
  };
98
113
 
99
114
  static defaultProps = {
100
115
  boundariesElement: typeof document !== 'undefined' ? document.body : undefined,
101
116
  };
117
+ private dropdownMenuRef = React.createRef<HTMLDivElement>();
118
+
119
+
120
+ componentDidMount() {
121
+ if (getBooleanFF('platform.editor.a11y-table-context-menu_y4c9c')) {
122
+ // ArrowKeyNavigationProvider in DropdownMenu expects that menu handle will stay focused
123
+ // until user pressed ArrowDown.
124
+ // Behavior above fails the A11Y requirement about first item in menu should be focused immediately.
125
+ // so here is triggering componentDidUpdate inside dropdown to set focus on first element
126
+ const { isCellMenuOpenByKeyboard } = this.props;
127
+ if (isCellMenuOpenByKeyboard) {
128
+ this.setState({
129
+ ...this.state,
130
+ isOpenAllowed: isCellMenuOpenByKeyboard
131
+ })
132
+ }
133
+ }
134
+ }
102
135
 
103
136
  render() {
104
- const { isOpen, mountPoint, offset, boundariesElement, editorView } = this.props;
137
+ const { isOpen, mountPoint, offset, boundariesElement, editorView, isCellMenuOpenByKeyboard } =
138
+ this.props;
105
139
  const { isDragAndDropEnabled } = getPluginState(editorView.state);
106
140
  const items = isDragAndDropEnabled
107
141
  ? this.createNewContextMenuItems()
108
142
  : this.createOriginalContextMenuItems();
143
+ let isOpenAllowed = false;
144
+
145
+ if (getBooleanFF('platform.editor.a11y-table-context-menu_y4c9c')) {
146
+ isOpenAllowed = isCellMenuOpenByKeyboard ? this.state.isOpenAllowed : isOpen;
147
+ } else {
148
+ isOpenAllowed = isOpen;
149
+ }
109
150
 
110
151
  return (
111
- <div data-testid="table-cell-contextual-menu" onMouseLeave={this.closeSubmenu}>
152
+ <div
153
+ data-testid="table-cell-contextual-menu"
154
+ onMouseLeave={this.closeSubmenu}
155
+ ref={this.dropdownMenuRef}
156
+ >
112
157
  <DropdownMenu
113
158
  mountTo={mountPoint}
114
159
  //This needs be removed when the a11y is completely handled
115
160
  //Disabling key navigation now as it works only partially
116
161
  arrowKeyNavigationProviderOptions={{
117
162
  type: ArrowKeyNavigationType.MENU,
118
- disableArrowKeyNavigation: true,
163
+ disableArrowKeyNavigation:
164
+ getBooleanFF('platform.editor.a11y-table-context-menu_y4c9c') &&
165
+ isCellMenuOpenByKeyboard &&
166
+ !this.state.isSubmenuOpen
167
+ ? false
168
+ : true,
119
169
  }}
120
170
  items={items}
121
- isOpen={isOpen}
171
+ isOpen={isOpenAllowed}
122
172
  onOpenChange={this.handleOpenChange}
123
173
  onItemActivated={this.onMenuItemActivated}
124
174
  onMouseEnter={this.handleItemMouseEnter}
@@ -127,9 +177,22 @@ export class ContextualMenu extends Component<Props & WrappedComponentProps, Sta
127
177
  fitWidth={
128
178
  isDragAndDropEnabled ? contextualMenuDropdownWidthDnD : contextualMenuDropdownWidth
129
179
  }
180
+ shouldFocusFirstItem={
181
+ getBooleanFF('platform.editor.a11y-table-context-menu_y4c9c')
182
+ ?
183
+ () => {
184
+ return Boolean(isCellMenuOpenByKeyboard);
185
+ }
186
+ : undefined
187
+ }
130
188
  boundariesElement={boundariesElement}
131
189
  offset={offset}
132
190
  section={isDragAndDropEnabled ? { hasSeparator: true } : undefined}
191
+ isAllowEnterDefaultBehavior={
192
+ getBooleanFF('platform.editor.a11y-table-context-menu_y4c9c')
193
+ ? this.state.isSubmenuOpen
194
+ : false
195
+ }
133
196
  />
134
197
  </div>
135
198
  );
@@ -157,6 +220,7 @@ export class ContextualMenu extends Component<Props & WrappedComponentProps, Sta
157
220
  isOpen,
158
221
  intl: { formatMessage },
159
222
  editorView,
223
+ isCellMenuOpenByKeyboard,
160
224
  } = this.props;
161
225
  const { isSubmenuOpen } = this.state;
162
226
  const { targetCellPosition, isDragAndDropEnabled } = getPluginState(editorView.state);
@@ -164,6 +228,18 @@ export class ContextualMenu extends Component<Props & WrappedComponentProps, Sta
164
228
  if (allowBackgroundColor) {
165
229
  const node = isOpen && targetCellPosition ? state.doc.nodeAt(targetCellPosition) : null;
166
230
  const background = hexToEditorBackgroundPaletteColor(node?.attrs?.background || '#ffffff');
231
+ let selectedRowIndex;
232
+ let selectedColumnIndex;
233
+
234
+ if (getBooleanFF('platform.editor.a11y-table-context-menu_y4c9c')) {
235
+ const selectedRowAndColumnFromPalette = getSelectedRowAndColumnFromPalette(
236
+ cellBackgroundColorPalette,
237
+ background!,
238
+ colorPalletteColumns,
239
+ );
240
+ selectedRowIndex = selectedRowAndColumnFromPalette.selectedRowIndex;
241
+ selectedColumnIndex = selectedRowAndColumnFromPalette.selectedColumnIndex;
242
+ }
167
243
  return {
168
244
  content: isDragAndDropEnabled
169
245
  ? formatMessage(messages.backgroundColor)
@@ -191,26 +267,64 @@ export class ContextualMenu extends Component<Props & WrappedComponentProps, Sta
191
267
  : ClassName.CONTEXTUAL_MENU_ICON
192
268
  }
193
269
  />
194
- {isSubmenuOpen && (
195
- <div
196
- // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
197
- className={ClassName.CONTEXTUAL_SUBMENU}
198
- ref={this.handleSubMenuRef}
199
- >
200
- <ColorPalette
201
- cols={7}
202
- onClick={this.setColor}
203
- selectedColor={node?.attrs?.background || '#ffffff'}
204
- paletteOptions={{
205
- palette: cellBackgroundColorPalette,
206
- paletteColorTooltipMessages: backgroundPaletteTooltipMessages,
207
- hexToPaletteColor: hexToEditorBackgroundPaletteColor,
208
- }}
209
- />
210
- </div>
211
- )}
270
+ {getBooleanFF('platform.editor.a11y-table-context-menu_y4c9c')
271
+ ? isSubmenuOpen && (
272
+ <div
273
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
274
+ className={ClassName.CONTEXTUAL_SUBMENU}
275
+ ref={this.handleSubMenuRef}
276
+ >
277
+ <ArrowKeyNavigationProvider
278
+ type={ArrowKeyNavigationType.COLOR}
279
+ selectedRowIndex={selectedRowIndex || 0}
280
+ selectedColumnIndex={selectedColumnIndex || 0}
281
+ handleClose={() => {
282
+ this.setState({ isSubmenuOpen: false });
283
+ if (this.dropdownMenuRef && this.dropdownMenuRef.current) {
284
+ const focusableItems = this.dropdownMenuRef.current.querySelectorAll(
285
+ 'div[tabindex="-1"]:not([disabled])',
286
+ ) as NodeListOf<HTMLElement>;
287
+ if (focusableItems && focusableItems.length) {
288
+ focusableItems[0].focus();
289
+ }
290
+ }
291
+ }}
292
+ isPopupPositioned={true}
293
+ isOpenedByKeyboard={isCellMenuOpenByKeyboard!}
294
+ >
295
+ <ColorPalette
296
+ cols={7}
297
+ onClick={this.setColor}
298
+ selectedColor={node?.attrs?.background || '#ffffff'}
299
+ paletteOptions={{
300
+ palette: cellBackgroundColorPalette,
301
+ paletteColorTooltipMessages: backgroundPaletteTooltipMessages,
302
+ hexToPaletteColor: hexToEditorBackgroundPaletteColor,
303
+ }}
304
+ />
305
+ </ArrowKeyNavigationProvider>
306
+ </div>
307
+ )
308
+ : isSubmenuOpen && (
309
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
310
+ <div className={ClassName.CONTEXTUAL_SUBMENU} ref={this.handleSubMenuRef}>
311
+ <ColorPalette
312
+ cols={7}
313
+ onClick={this.setColor}
314
+ selectedColor={node?.attrs?.background || '#ffffff'}
315
+ paletteOptions={{
316
+ palette: cellBackgroundColorPalette,
317
+ paletteColorTooltipMessages: backgroundPaletteTooltipMessages,
318
+ hexToPaletteColor: hexToEditorBackgroundPaletteColor,
319
+ }}
320
+ />
321
+ </div>
322
+ )}
212
323
  </div>
213
324
  ),
325
+ 'aria-expanded': getBooleanFF('platform.editor.a11y-table-context-menu_y4c9c')
326
+ ? isSubmenuOpen
327
+ : undefined,
214
328
  } as MenuItem;
215
329
  }
216
330
  };
@@ -526,6 +640,7 @@ export class ContextualMenu extends Component<Props & WrappedComponentProps, Sta
526
640
  editorAnalyticsAPI,
527
641
  getEditorContainerWidth,
528
642
  getEditorFeatureFlags,
643
+ isCellMenuOpenByKeyboard,
529
644
  } = this.props;
530
645
  // TargetCellPosition could be outdated: https://product-fabric.atlassian.net/browse/ED-8129
531
646
  const { state, dispatch } = editorView;
@@ -533,6 +648,24 @@ export class ContextualMenu extends Component<Props & WrappedComponentProps, Sta
533
648
 
534
649
  const { tableDuplicateCellColouring = false, tableWithFixedColumnWidthsOption = false } =
535
650
  getEditorFeatureFlags ? getEditorFeatureFlags() : {};
651
+ // context menu opened by keyboard and any item except 'background' activated
652
+ // or color has been chosen from color palette
653
+ if (
654
+ getBooleanFF('platform.editor.a11y-table-context-menu_y4c9c') &&
655
+ isCellMenuOpenByKeyboard &&
656
+ (item.value.name !== 'background' ||
657
+ (item.value.name === 'background' && this.state.isSubmenuOpen))
658
+ ) {
659
+ const { tr } = state;
660
+ tr.setMeta(tablePluginKey, {
661
+ type: 'SET_CELL_MENU_OPEN',
662
+ data: {
663
+ isCellMenuOpenByKeyboard: false,
664
+ },
665
+ });
666
+ dispatch(tr);
667
+ editorView.dom.focus(); // otherwise cursor disappears from cell
668
+ }
536
669
 
537
670
  const shouldUseIncreasedScalingPercent =
538
671
  isTableScalingEnabled &&
@@ -630,6 +763,20 @@ export class ContextualMenu extends Component<Props & WrappedComponentProps, Sta
630
763
  )(state, dispatch);
631
764
  this.toggleOpen();
632
765
  break;
766
+ case 'background': {
767
+ // This is called twice.
768
+ // 1st time when user chooses the background color item.
769
+ // 2nd when color has been chosen from color palette.
770
+ // here we are handling the 1st call relying on the isSubmenuOpen state value
771
+ if (
772
+ getBooleanFF('platform.editor.a11y-table-context-menu_y4c9c') &&
773
+ isCellMenuOpenByKeyboard &&
774
+ !this.state.isSubmenuOpen
775
+ ) {
776
+ this.setState({ isSubmenuOpen: true });
777
+ }
778
+ break;
779
+ }
633
780
  }
634
781
  };
635
782
 
@@ -646,12 +793,44 @@ export class ContextualMenu extends Component<Props & WrappedComponentProps, Sta
646
793
  }
647
794
  };
648
795
 
649
- private handleOpenChange = () => {
796
+ private handleOpenChange = (payload?: {
797
+ event: PointerEvent | KeyboardEvent;
798
+ isOpen: boolean;
799
+ }) => {
650
800
  const {
651
- editorView: { state, dispatch },
801
+ editorView: { state, dispatch, dom },
802
+ isCellMenuOpenByKeyboard,
652
803
  } = this.props;
653
- toggleContextualMenu()(state, dispatch);
654
- this.setState({ isSubmenuOpen: false });
804
+
805
+ if (getBooleanFF('platform.editor.a11y-table-context-menu_y4c9c')) {
806
+ if (payload) {
807
+ const { event } = payload;
808
+ if (event && event instanceof KeyboardEvent) {
809
+ if (!this.state.isSubmenuOpen) {
810
+ if (arrowsList.has(event.key)) {
811
+ // preventing default behavior for avoiding cursor jump to next/previous table column
812
+ // when left/right arrow pressed.
813
+ event.preventDefault();
814
+ }
815
+
816
+ toggleContextualMenu()(state, dispatch);
817
+ this.setState({ isSubmenuOpen: false });
818
+ setFocusToCellMenu(false)(state, dispatch);
819
+ dom.focus();
820
+ }
821
+ } else {
822
+ // mouse click outside
823
+ toggleContextualMenu()(state, dispatch);
824
+ this.setState({ isSubmenuOpen: false });
825
+ if (isCellMenuOpenByKeyboard) {
826
+ setFocusToCellMenu(false)(state, dispatch);
827
+ }
828
+ }
829
+ }
830
+ } else {
831
+ toggleContextualMenu()(state, dispatch);
832
+ this.setState({ isSubmenuOpen: false });
833
+ }
655
834
  };
656
835
 
657
836
  private handleItemMouseEnter = ({ item }: { item: any }) => {
@@ -40,6 +40,7 @@ export interface Props {
40
40
  scrollableElement?: HTMLElement;
41
41
  pluginConfig?: PluginConfig;
42
42
  editorAnalyticsAPI?: EditorAnalyticsAPI;
43
+ isCellMenuOpenByKeyboard?: boolean;
43
44
  }
44
45
 
45
46
  const FloatingContextualMenu = ({
@@ -52,6 +53,7 @@ const FloatingContextualMenu = ({
52
53
  editorAnalyticsAPI,
53
54
  getEditorContainerWidth,
54
55
  getEditorFeatureFlags,
56
+ isCellMenuOpenByKeyboard,
55
57
  }: Props) => {
56
58
  // TargetCellPosition could be outdated: https://product-fabric.atlassian.net/browse/ED-8129
57
59
  const { targetCellPosition, isDragAndDropEnabled } = getPluginState(editorView.state);
@@ -108,6 +110,7 @@ const FloatingContextualMenu = ({
108
110
  editorAnalyticsAPI={editorAnalyticsAPI}
109
111
  getEditorContainerWidth={getEditorContainerWidth}
110
112
  getEditorFeatureFlags={getEditorFeatureFlags}
113
+ isCellMenuOpenByKeyboard={isCellMenuOpenByKeyboard}
111
114
  />
112
115
  </div>
113
116
  </Popup>