@atlaskit/editor-common 70.3.0 → 71.0.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 (75) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/cjs/analytics/types/enums.js +1 -0
  3. package/dist/cjs/keymaps/index.js +5 -8
  4. package/dist/cjs/messages/insert-block.js +12 -7
  5. package/dist/cjs/panel.js +6 -0
  6. package/dist/cjs/styles/shared/code-block.js +5 -5
  7. package/dist/cjs/styles/shared/panel.js +54 -17
  8. package/dist/cjs/styles/shared/table.js +12 -7
  9. package/dist/cjs/styles/shared/text-color.js +1 -1
  10. package/dist/cjs/ui/DropList/index.js +1 -1
  11. package/dist/cjs/ui-color/ColorPalette/Color/index.js +6 -2
  12. package/dist/cjs/ui-color/ColorPalette/index.js +21 -11
  13. package/dist/cjs/ui-menu/Dropdown/index.js +16 -2
  14. package/dist/cjs/ui-menu/DropdownMenu/index.js +27 -8
  15. package/dist/cjs/ui-menu/MenuArrowKeyNavigationProvider/index.js +162 -0
  16. package/dist/cjs/ui-react/with-react-editor-view-outer-listeners.js +22 -13
  17. package/dist/cjs/utils/index.js +6 -0
  18. package/dist/cjs/utils/performance/measure-render.js +44 -23
  19. package/dist/cjs/version.json +1 -1
  20. package/dist/es2019/analytics/types/enums.js +1 -0
  21. package/dist/es2019/keymaps/index.js +4 -6
  22. package/dist/es2019/messages/insert-block.js +12 -7
  23. package/dist/es2019/panel.js +1 -1
  24. package/dist/es2019/styles/shared/code-block.js +39 -25
  25. package/dist/es2019/styles/shared/panel.js +48 -18
  26. package/dist/es2019/styles/shared/table.js +24 -13
  27. package/dist/es2019/styles/shared/text-color.js +1 -1
  28. package/dist/es2019/ui/DropList/index.js +1 -1
  29. package/dist/es2019/ui-color/ColorPalette/Color/index.js +6 -3
  30. package/dist/es2019/ui-color/ColorPalette/index.js +19 -11
  31. package/dist/es2019/ui-menu/Dropdown/index.js +13 -2
  32. package/dist/es2019/ui-menu/DropdownMenu/index.js +41 -6
  33. package/dist/es2019/ui-menu/MenuArrowKeyNavigationProvider/index.js +146 -0
  34. package/dist/es2019/ui-react/with-react-editor-view-outer-listeners.js +20 -13
  35. package/dist/es2019/utils/index.js +1 -1
  36. package/dist/es2019/utils/performance/measure-render.js +43 -23
  37. package/dist/es2019/version.json +1 -1
  38. package/dist/esm/analytics/types/enums.js +1 -0
  39. package/dist/esm/keymaps/index.js +3 -6
  40. package/dist/esm/messages/insert-block.js +12 -7
  41. package/dist/esm/panel.js +1 -1
  42. package/dist/esm/styles/shared/code-block.js +5 -5
  43. package/dist/esm/styles/shared/panel.js +49 -18
  44. package/dist/esm/styles/shared/table.js +12 -9
  45. package/dist/esm/styles/shared/text-color.js +1 -1
  46. package/dist/esm/ui/DropList/index.js +1 -1
  47. package/dist/esm/ui-color/ColorPalette/Color/index.js +5 -2
  48. package/dist/esm/ui-color/ColorPalette/index.js +20 -11
  49. package/dist/esm/ui-menu/Dropdown/index.js +15 -2
  50. package/dist/esm/ui-menu/DropdownMenu/index.js +28 -9
  51. package/dist/esm/ui-menu/MenuArrowKeyNavigationProvider/index.js +147 -0
  52. package/dist/esm/ui-react/with-react-editor-view-outer-listeners.js +22 -13
  53. package/dist/esm/utils/index.js +1 -1
  54. package/dist/esm/utils/performance/measure-render.js +42 -23
  55. package/dist/esm/version.json +1 -1
  56. package/dist/types/analytics/types/enums.d.ts +1 -0
  57. package/dist/types/analytics/types/general-events.d.ts +2 -1
  58. package/dist/types/keymaps/index.d.ts +0 -1
  59. package/dist/types/messages/insert-block.d.ts +7 -2
  60. package/dist/types/panel.d.ts +1 -1
  61. package/dist/types/styles/shared/code-block.d.ts +1 -0
  62. package/dist/types/styles/shared/panel.d.ts +1 -0
  63. package/dist/types/types/feature-flags.d.ts +27 -6
  64. package/dist/types/types/floating-toolbar.d.ts +4 -0
  65. package/dist/types/ui-color/ColorPalette/Color/index.d.ts +5 -0
  66. package/dist/types/ui-color/ColorPalette/index.d.ts +8 -0
  67. package/dist/types/ui-menu/Dropdown/index.d.ts +3 -0
  68. package/dist/types/ui-menu/DropdownMenu/index.d.ts +1 -0
  69. package/dist/types/ui-menu/DropdownMenu/types.d.ts +7 -0
  70. package/dist/types/ui-menu/MenuArrowKeyNavigationProvider/index.d.ts +15 -0
  71. package/dist/types/ui-react/with-react-editor-view-outer-listeners.d.ts +2 -0
  72. package/dist/types/utils/index.d.ts +1 -1
  73. package/dist/types/utils/performance/measure-render.d.ts +12 -0
  74. package/package.json +8 -7
  75. package/report.api.md +12 -5
@@ -1,5 +1,3 @@
1
- /* eslint-disable @atlaskit/design-system/ensure-design-token-usage */
2
- // TODO: https://product-fabric.atlassian.net/browse/DSP-4066
3
1
  import { css } from '@emotion/react';
4
2
  import { PanelType } from '@atlaskit/adf-schema';
5
3
  import { akEditorTableCellMinWidth, blockNodesVerticalMargin } from '@atlaskit/editor-shared-styles';
@@ -8,7 +6,19 @@ import { emojiImage, emojiSprite } from '@atlaskit/emoji';
8
6
  import * as colors from '@atlaskit/theme/colors';
9
7
  import { themed } from '@atlaskit/theme/components';
10
8
  import { borderRadius, gridSize } from '@atlaskit/theme/constants';
9
+ import { token } from '@atlaskit/tokens';
10
+ const tokenPanelColor = {
11
+ info: 'color.background.information',
12
+ note: 'color.background.discovery',
13
+ tip: 'color.background.success',
14
+ success: 'color.background.success',
15
+ warning: 'color.background.warning',
16
+ error: 'color.background.danger'
17
+ };
11
18
  const lightPanelColor = {
19
+ // TODO: https://product-fabric.atlassian.net/browse/DSP-4066
20
+
21
+ /* eslint-disable @atlaskit/design-system/ensure-design-token-usage */
12
22
  info: colors.B50,
13
23
  note: colors.P50,
14
24
  tip: colors.G50,
@@ -18,7 +28,7 @@ const lightPanelColor = {
18
28
  };
19
29
  export const darkPanelColors = {
20
30
  // standard panels
21
- info: '#0C294F',
31
+ info: `#0C294F`,
22
32
  error: `#441C13`,
23
33
  warning: `#413001`,
24
34
  tip: `#052E21`,
@@ -78,26 +88,31 @@ export const darkPanelColors = {
78
88
  LightGray: '#5A6977',
79
89
  TextColor: '#D9DDE3'
80
90
  };
91
+ /* eslint-enable @atlaskit/design-system/ensure-design-token-usage */
92
+
81
93
  const lightIconColor = {
82
- info: colors.B400,
83
- note: colors.P400,
84
- tip: colors.G400,
85
- success: colors.G400,
86
- warning: colors.Y400,
87
- error: colors.R400
94
+ info: token('color.icon.information', colors.B400),
95
+ note: token('color.icon.discovery', colors.P400),
96
+ tip: token('color.icon.success', colors.G400),
97
+ success: token('color.icon.success', colors.G400),
98
+ warning: token('color.icon.warning', colors.Y400),
99
+ error: token('color.icon.danger', colors.R400)
88
100
  };
89
101
  const darkIconColor = {
90
- info: colors.B100,
91
- note: colors.P100,
92
- tip: colors.G200,
93
- success: colors.G200,
94
- warning: colors.Y100,
95
- error: colors.R200
102
+ info: token('color.icon.information', colors.B100),
103
+ note: token('color.icon.discovery', colors.P100),
104
+ tip: token('color.icon.success', colors.G200),
105
+ success: token('color.icon.success', colors.G200),
106
+ warning: token('color.icon.warning', colors.Y100),
107
+ error: token('color.icon.danger', colors.R200)
96
108
  }; // New custom icons are a little smaller than predefined icons.
97
109
  // To fix alignment issues with custom icons, vertical alignment is updated.
98
110
 
99
111
  const panelEmojiSpriteVerticalAlignment = -(gridSize() * 3 - akEditorCustomIconSize) / 2;
100
- const panelEmojiImageVerticalAlignment = panelEmojiSpriteVerticalAlignment - 1;
112
+ const panelEmojiImageVerticalAlignment = panelEmojiSpriteVerticalAlignment - 1; // TODO: https://product-fabric.atlassian.net/browse/DSP-4066
113
+
114
+ /* eslint-disable @atlaskit/design-system/ensure-design-token-usage */
115
+
101
116
  const panelDarkModeColors = [[colors.B50, darkPanelColors.B1200S], [colors.B75, darkPanelColors.B900], [colors.B100, darkPanelColors.B800S], [colors.N0, darkPanelColors.LightGray], [colors.N20, darkPanelColors.Gray], [colors.N60, darkPanelColors.DarkGray], [colors.T50, darkPanelColors.T1200S], [colors.T75, darkPanelColors.T900], [colors.T100, darkPanelColors.T900S], [colors.G50, darkPanelColors.G1200S], [colors.G75, darkPanelColors.G900], [colors.G200, darkPanelColors.G900S], [colors.Y50, darkPanelColors.Y1200S], [colors.Y75, darkPanelColors.Y900], [colors.Y200, darkPanelColors.Y800S], [colors.R50, darkPanelColors.R1200S], [colors.R75, darkPanelColors.R900], [colors.R100, darkPanelColors.R800S], [colors.P50, darkPanelColors.P1200S], [colors.P75, darkPanelColors.P900], [colors.P100, darkPanelColors.P800S]];
102
117
  export const getPanelDarkColor = panelColor => {
103
118
  const colorObject = panelDarkModeColors.find(color => color[0] === panelColor || color[1] === panelColor);
@@ -152,9 +167,10 @@ const iconDynamicStyles = panelType => props => {
152
167
  return `
153
168
  color: ${color};
154
169
  `;
155
- };
170
+ }; // Provides the color without tokens, used when converting to a custom panel
156
171
 
157
- export const getPanelTypeBackground = (panelType, props = {}) => {
172
+
173
+ export const getPanelTypeBackgroundNoTokens = (panelType, props = {}) => {
158
174
  const light = lightPanelColor[panelType];
159
175
  const dark = darkPanelColors[panelType];
160
176
  const background = themed({
@@ -163,6 +179,20 @@ export const getPanelTypeBackground = (panelType, props = {}) => {
163
179
  })(props);
164
180
  return background || 'none';
165
181
  };
182
+ export const getPanelTypeBackground = (panelType, props = {}) => {
183
+ // TODO: https://product-fabric.atlassian.net/browse/DSP-4066
184
+
185
+ /* eslint-disable @atlaskit/design-system/no-unsafe-design-token-usage */
186
+ const light = token(tokenPanelColor[panelType], lightPanelColor[panelType]);
187
+ const dark = token(tokenPanelColor[panelType], darkPanelColors[panelType]);
188
+ /* eslint-disable @atlaskit/design-system/no-unsafe-design-token-usage */
189
+
190
+ const background = themed({
191
+ light,
192
+ dark
193
+ })(props);
194
+ return background || 'none';
195
+ };
166
196
 
167
197
  const mainDynamicStyles = panelType => props => {
168
198
  const background = getPanelTypeBackground(panelType, props);
@@ -1,14 +1,14 @@
1
1
  /* eslint-disable @atlaskit/design-system/ensure-design-token-usage */
2
2
  // TODO: https://product-fabric.atlassian.net/browse/DSP-4118
3
- // TODO: https://product-fabric.atlassian.net/browse/DSP-4153
4
3
  import { css } from '@emotion/react';
5
4
  import { tableCellContentDomSelector, tableCellSelector, tableHeaderSelector, tablePrefixSelector } from '@atlaskit/adf-schema';
6
- import { akEditorBreakoutPadding, akEditorFullWidthLayoutWidth, akEditorTableBorder, akEditorTableBorderDark, akEditorTableNumberColumnWidth, akEditorTableToolbar, akEditorTableToolbarDark, akEditorWideLayoutWidth, getTableCellBackgroundDarkModeColors, overflowShadow } from '@atlaskit/editor-shared-styles';
5
+ import { akEditorBreakoutPadding, akEditorFullWidthLayoutWidth, akEditorSelectedNodeClassName, akEditorTableBorder, akEditorTableBorderDark, akEditorTableNumberColumnWidth, akEditorTableToolbar, akEditorTableToolbarDark, akEditorWideLayoutWidth, getTableCellBackgroundDarkModeColors, overflowShadow } from '@atlaskit/editor-shared-styles';
7
6
  import { DN20 } from '@atlaskit/theme/colors';
8
7
  import { themed } from '@atlaskit/theme/components';
9
8
  import { gridSize } from '@atlaskit/theme/constants';
10
9
  import { token } from '@atlaskit/tokens';
11
10
  import browser from '../../utils/browser';
11
+ import { CodeBlockSharedCssClassName } from './code-block';
12
12
  export const tableMarginTop = 24;
13
13
  export const tableMarginBottom = 16;
14
14
  export const tableMarginSides = 8;
@@ -140,23 +140,34 @@ const tableSharedStyle = props => css`
140
140
 
141
141
  /* only apply this styling to codeblocks in default background headercells */
142
142
  /* TODO this needs to be overhauled as it relies on unsafe selectors */
143
- &:not([style]) {
144
- .code-block {
145
- background-image: ${overflowShadow({
143
+ &:not([style]):not(.danger) {
144
+ .${CodeBlockSharedCssClassName.CODEBLOCK_CONTAINER}:not(.danger) {
145
+ background-color: ${themed({
146
+ light: token('elevation.surface.raised', 'rgb(235, 237, 240)'),
147
+ dark: token('elevation.surface.raised', 'rgb(36, 47, 66)')
148
+ })(props)};
149
+
150
+ :not(.${akEditorSelectedNodeClassName}) {
151
+ box-shadow: 0px 0px 0px 1px
152
+ ${token('color.border', 'transparent')};
153
+ }
154
+
155
+ .${CodeBlockSharedCssClassName.CODEBLOCK_CONTENT_WRAPPER} {
156
+ background-image: ${overflowShadow({
146
157
  background: themed({
147
- light: 'rgb(235, 237, 240)',
148
- dark: 'rgb(36, 47, 66)'
158
+ light: token('color.background.neutral', 'rgb(235, 237, 240)'),
159
+ dark: token('color.background.neutral', 'rgb(36, 47, 66)')
149
160
  })(props),
150
161
  width: `${gridSize()}px`
151
162
  })};
152
- background-attachment: local, scroll, scroll;
153
- background-position: 100% 0, 100% 0, 0 0;
154
- background-color: ${themed({
163
+
164
+ background-color: ${themed({
155
165
  light: token('color.background.neutral', 'rgb(235, 237, 240)'),
156
166
  dark: token('color.background.neutral', 'rgb(36, 47, 66)')
157
167
  })(props)};
168
+ }
158
169
 
159
- .line-number-gutter {
170
+ .${CodeBlockSharedCssClassName.CODEBLOCK_LINE_NUMBER_GUTTER} {
160
171
  background-color: ${themed({
161
172
  light: token('color.background.neutral', 'rgb(226, 229, 233)'),
162
173
  dark: token('color.background.neutral', DN20)
@@ -167,8 +178,8 @@ const tableSharedStyle = props => css`
167
178
  > [data-ds--code--code-block] {
168
179
  background-image: ${overflowShadow({
169
180
  background: themed({
170
- light: 'rgb(235, 237, 240)',
171
- dark: 'rgb(36, 47, 66)'
181
+ light: token('color.background.neutral', 'rgb(235, 237, 240)'),
182
+ dark: token('color.background.neutral', 'rgb(36, 47, 66)')
172
183
  })(props),
173
184
  width: `${gridSize()}px`
174
185
  })}!important;
@@ -1,7 +1,7 @@
1
1
  import { css } from '@emotion/react';
2
2
  export const textColorStyles = css`
3
3
  .fabric-text-color-mark {
4
- color: var(--custom-text-color, inherit);
4
+ color: var(--custom-palette-color, inherit);
5
5
  }
6
6
 
7
7
  a .fabric-text-color-mark {
@@ -10,7 +10,7 @@ import { borderRadius, gridSize } from '@atlaskit/theme/constants';
10
10
  import { token } from '@atlaskit/tokens';
11
11
  import Layer from '../Layer';
12
12
  const packageName = "@atlaskit/editor-common";
13
- const packageVersion = "70.3.0";
13
+ const packageVersion = "71.0.1";
14
14
  const halfFocusRing = 1;
15
15
  const dropOffset = `0, ${gridSize()}px`;
16
16
 
@@ -3,6 +3,7 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
3
  /** @jsx jsx */
4
4
  import React, { PureComponent } from 'react';
5
5
  import { jsx } from '@emotion/react';
6
+ import { hexToEditorTextPaletteColor } from '@atlaskit/editor-palette';
6
7
  import EditorDoneIcon from '@atlaskit/icon/glyph/editor/done';
7
8
  import { N0 } from '@atlaskit/theme/colors';
8
9
  import Tooltip from '@atlaskit/tooltip';
@@ -39,10 +40,12 @@ class Color extends PureComponent {
39
40
  /** this is not new usage - old code extracted from editor-core */
40
41
 
41
42
  /* eslint-disable @atlaskit/design-system/ensure-design-token-usage */
42
- checkMarkColor = N0
43
- /* eslint-enable @atlaskit/design-system/ensure-design-token-usage */
43
+ checkMarkColor = N0,
44
44
 
45
+ /* eslint-enable @atlaskit/design-system/ensure-design-token-usage */
46
+ useDesignTokens
45
47
  } = this.props;
48
+ const colorStyle = useDesignTokens ? hexToEditorTextPaletteColor(value) : value;
46
49
  return jsx(Tooltip, {
47
50
  content: label
48
51
  }, jsx("span", {
@@ -57,7 +60,7 @@ class Color extends PureComponent {
57
60
  tabIndex: tabIndex,
58
61
  className: `${isSelected ? 'selected' : ''}`,
59
62
  style: {
60
- backgroundColor: value || 'transparent',
63
+ backgroundColor: colorStyle || 'transparent',
61
64
  border: `1px solid ${borderColor}`
62
65
  },
63
66
  autoFocus: autoFocus
@@ -4,6 +4,7 @@ import { jsx } from '@emotion/react';
4
4
  import chromatism from 'chromatism';
5
5
  import { injectIntl } from 'react-intl-next';
6
6
  import { N0, N500 } from '@atlaskit/theme/colors';
7
+ import { token } from '@atlaskit/tokens';
7
8
  import Color from './Color';
8
9
  import { colorPaletteWrapper } from './styles';
9
10
 
@@ -14,8 +15,19 @@ import { colorPaletteWrapper } from './styles';
14
15
  * @param color color string, suppports HEX, RGB, RGBA etc.
15
16
  * @return Highest contrast color in pool
16
17
  */
17
- function getContrastColor(color, pool) {
18
- return pool.sort((a, b) => chromatism.difference(b, color) - chromatism.difference(a, color))[0];
18
+ function getCheckMarkColor(color, textPalette) {
19
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
20
+ const contrastColor = [N0, N500].sort((a, b) => chromatism.difference(b, color) - chromatism.difference(a, color))[0];
21
+
22
+ if (!textPalette) {
23
+ return contrastColor;
24
+ } // Use of these token comes from guidance from designers in the Design System team
25
+ // they are only intended for use with text colors (and there are different tokens
26
+ // planned to be used when this extended to be used with other palettes).
27
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
28
+
29
+
30
+ return contrastColor === N0 ? token('color.icon.inverse', N0) : token('color.icon', N500);
19
31
  }
20
32
 
21
33
  const ColorPalette = props => {
@@ -27,7 +39,8 @@ const ColorPalette = props => {
27
39
  className,
28
40
  intl: {
29
41
  formatMessage
30
- }
42
+ },
43
+ textPalette = false
31
44
  } = props;
32
45
  const colorsPerRow = React.useMemo(() => {
33
46
  return palette.reduce((resultArray, item, index) => {
@@ -54,14 +67,9 @@ const ColorPalette = props => {
54
67
  borderColor: border,
55
68
  label: message ? formatMessage(message) : label,
56
69
  onClick: onClick,
57
- isSelected: value === selectedColor
58
- /** this is not new usage - old code extracted from editor-core */
59
-
60
- /* eslint-disable @atlaskit/design-system/ensure-design-token-usage */
61
- ,
62
- checkMarkColor: getContrastColor(value, [N0, N500])
63
- /* eslint-enable @atlaskit/design-system/ensure-design-token-usage */
64
-
70
+ isSelected: value === selectedColor,
71
+ checkMarkColor: getCheckMarkColor(value, textPalette),
72
+ useDesignTokens: textPalette === true
65
73
  })))));
66
74
  };
67
75
 
@@ -3,6 +3,7 @@ import React, { PureComponent } from 'react';
3
3
  import { withReactEditorViewOuterListeners } from '../../ui-react';
4
4
  import DropdownList from '../../ui/DropList';
5
5
  import Popup from '../../ui/Popup';
6
+ import { MenuArrowKeyNavigationProvider } from '../MenuArrowKeyNavigationProvider';
6
7
 
7
8
  /**
8
9
  * Wrapper around @atlaskit/droplist which uses Popup and Portal to render
@@ -44,7 +45,9 @@ export class Dropdown extends PureComponent {
44
45
  onOpenChange,
45
46
  fitHeight,
46
47
  fitWidth,
47
- zIndex
48
+ zIndex,
49
+ disableArrowKeyNavigation,
50
+ keyDownHandlerContext
48
51
  } = this.props;
49
52
  return /*#__PURE__*/React.createElement(Popup, {
50
53
  target: target,
@@ -55,6 +58,14 @@ export class Dropdown extends PureComponent {
55
58
  fitHeight: fitHeight,
56
59
  fitWidth: fitWidth,
57
60
  zIndex: zIndex
61
+ }, /*#__PURE__*/React.createElement(MenuArrowKeyNavigationProvider, {
62
+ disableArrowKeyNavigation: disableArrowKeyNavigation,
63
+ keyDownHandlerContext: keyDownHandlerContext,
64
+ closeonTab: true,
65
+ handleClose: event => onOpenChange && onOpenChange({
66
+ isOpen: false,
67
+ event
68
+ })
58
69
  }, /*#__PURE__*/React.createElement("div", {
59
70
  style: {
60
71
  height: 0,
@@ -65,7 +76,7 @@ export class Dropdown extends PureComponent {
65
76
  onOpenChange: onOpenChange,
66
77
  position: popupPlacement.join(' '),
67
78
  shouldFitContainer: true
68
- }, children)));
79
+ }, children))));
69
80
  }
70
81
 
71
82
  render() {
@@ -6,19 +6,24 @@ import React, { PureComponent } from 'react';
6
6
  import { css, jsx } from '@emotion/react';
7
7
  import { akEditorFloatingPanelZIndex } from '@atlaskit/editor-shared-styles';
8
8
  import { CustomItem, MenuGroup } from '@atlaskit/menu';
9
- import { DN600, DN80, N70, N900 } from '@atlaskit/theme/colors';
9
+ import { B100, DN600, DN80, N70, N900 } from '@atlaskit/theme/colors';
10
10
  import { themed } from '@atlaskit/theme/components';
11
11
  import { token } from '@atlaskit/tokens';
12
12
  import Tooltip from '@atlaskit/tooltip';
13
13
  import { withReactEditorViewOuterListeners } from '../../ui-react';
14
14
  import DropList from '../../ui/DropList';
15
15
  import Popup from '../../ui/Popup';
16
+ import { MenuArrowKeyNavigationProvider } from '../MenuArrowKeyNavigationProvider';
16
17
  const wrapper = css`
17
18
  /* tooltip in ToolbarButton is display:block */
18
19
  & > div > div {
19
20
  display: flex;
20
21
  }
21
22
  `;
23
+ const focusedMenuItemStyle = css`
24
+ box-shadow: inset 0px 0px 0px 2px ${token('color.border.focused', B100)};
25
+ outline: none;
26
+ `;
22
27
 
23
28
  const buttonStyles = isActive => theme => {
24
29
  if (isActive) {
@@ -32,6 +37,13 @@ const buttonStyles = isActive => theme => {
32
37
  background: ${token('color.background.selected', '#6c798f')};
33
38
  color: ${token('color.text', '#fff')};
34
39
  }
40
+ :focus > span[aria-disabled='false'] {
41
+ ${focusedMenuItemStyle};
42
+ }
43
+ :focus-visible,
44
+ :focus-visible > span[aria-disabled='false'] {
45
+ outline: none;
46
+ }
35
47
  `;
36
48
  } else {
37
49
  return css`
@@ -57,7 +69,14 @@ const buttonStyles = isActive => theme => {
57
69
  dark: token('color.text.disabled', DN80)
58
70
  })(theme)};
59
71
  }
60
- `;
72
+ :focus > span[aria-disabled='false'] {
73
+ ${focusedMenuItemStyle};
74
+ }
75
+ :focus-visible,
76
+ :focus-visible > span[aria-disabled='false'] {
77
+ outline: none;
78
+ }
79
+ `; // The deafut focus-visible style is removed to ensure consistency across browsers
61
80
  }
62
81
  };
63
82
 
@@ -95,6 +114,13 @@ export default class DropdownMenuWrapper extends PureComponent {
95
114
  }
96
115
  });
97
116
 
117
+ _defineProperty(this, "handleCloseandFocus", () => {
118
+ var _this$state$target, _this$state$target$qu;
119
+
120
+ this.handleClose();
121
+ (_this$state$target = this.state.target) === null || _this$state$target === void 0 ? void 0 : (_this$state$target$qu = _this$state$target.querySelector('button')) === null || _this$state$target$qu === void 0 ? void 0 : _this$state$target$qu.focus();
122
+ });
123
+
98
124
  _defineProperty(this, "handleClose", () => {
99
125
  if (this.props.onOpenChange) {
100
126
  this.props.onOpenChange({
@@ -119,7 +145,9 @@ export default class DropdownMenuWrapper extends PureComponent {
119
145
  fitWidth,
120
146
  isOpen,
121
147
  zIndex,
122
- shouldUseDefaultRole
148
+ shouldUseDefaultRole,
149
+ disableArrowKeyNavigation,
150
+ keyDownHandlerContext
123
151
  } = this.props;
124
152
  return jsx(Popup, {
125
153
  target: isOpen ? target : undefined,
@@ -131,6 +159,11 @@ export default class DropdownMenuWrapper extends PureComponent {
131
159
  fitWidth: fitWidth,
132
160
  zIndex: zIndex || akEditorFloatingPanelZIndex,
133
161
  offset: offset
162
+ }, jsx(MenuArrowKeyNavigationProvider, {
163
+ disableArrowKeyNavigation: disableArrowKeyNavigation,
164
+ handleClose: this.handleCloseandFocus,
165
+ keyDownHandlerContext: keyDownHandlerContext,
166
+ closeonTab: true
134
167
  }, jsx(DropListWithOutsideListeners, {
135
168
  isOpen: true,
136
169
  appearance: "tall",
@@ -139,7 +172,8 @@ export default class DropdownMenuWrapper extends PureComponent {
139
172
  shouldFitContainer: true,
140
173
  isTriggerNotTabbable: true,
141
174
  handleClickOutside: this.handleClose,
142
- handleEscapeKeydown: this.handleClose
175
+ handleEscapeKeydown: this.handleCloseandFocus,
176
+ targetRef: this.state.target
143
177
  }, jsx("div", {
144
178
  style: {
145
179
  height: 0,
@@ -159,7 +193,7 @@ export default class DropdownMenuWrapper extends PureComponent {
159
193
  onMouseEnter: this.props.onMouseEnter,
160
194
  onMouseLeave: this.props.onMouseLeave
161
195
  });
162
- })))));
196
+ }))))));
163
197
  }
164
198
 
165
199
  render() {
@@ -205,7 +239,8 @@ function DropdownMenuItem({
205
239
  const dropListItem = jsx("div", {
206
240
  css: theme => buttonStyles(item.isActive)({
207
241
  theme
208
- })
242
+ }),
243
+ tabIndex: -1
209
244
  }, jsx(CustomItem, {
210
245
  item: item,
211
246
  key: (_item$key2 = item.key) !== null && _item$key2 !== void 0 ? _item$key2 : String(item.content),
@@ -0,0 +1,146 @@
1
+ import React, { useLayoutEffect, useRef } from 'react';
2
+
3
+ /**
4
+ * This component is a wrapper of vertical menus which listens to keydown events of children
5
+ * and handles up/down arrow key navigation
6
+ */
7
+ export const MenuArrowKeyNavigationProvider = ({
8
+ children,
9
+ handleClose,
10
+ disableArrowKeyNavigation,
11
+ keyDownHandlerContext,
12
+ closeonTab
13
+ }) => {
14
+ const wrapperRef = useRef(null);
15
+ const currentSelectedItemIndex = useRef(-1);
16
+
17
+ const incrementIndex = list => {
18
+ if (currentSelectedItemIndex.current === list.length - 1 || currentSelectedItemIndex.current === -1) {
19
+ currentSelectedItemIndex.current = 0;
20
+ } else {
21
+ currentSelectedItemIndex.current = currentSelectedItemIndex.current + 1;
22
+ }
23
+ };
24
+
25
+ const decrementIndex = list => {
26
+ if (currentSelectedItemIndex.current === 0 || currentSelectedItemIndex.current === -1) {
27
+ currentSelectedItemIndex.current = list.length - 1;
28
+ } else {
29
+ currentSelectedItemIndex.current = currentSelectedItemIndex.current - 1;
30
+ }
31
+ };
32
+
33
+ useLayoutEffect(() => {
34
+ if (disableArrowKeyNavigation) {
35
+ return;
36
+ }
37
+ /**
38
+ * To handle the key events on the list
39
+ * @param event
40
+ */
41
+
42
+
43
+ const handleKeyDown = event => {
44
+ var _wrapperRef$current, _focusableElements$cu, _focusableElements$cu2;
45
+
46
+ const targetElement = event.target; //Tab key on menu items can be handled in the parent components of dropdown menus with KeydownHandlerContext
47
+
48
+ if (event.key === 'Tab' && closeonTab) {
49
+ handleClose(event);
50
+ keyDownHandlerContext === null || keyDownHandlerContext === void 0 ? void 0 : keyDownHandlerContext.handleTab();
51
+ return;
52
+ } //To trap the focus inside the toolbar using left and right arrow keys
53
+
54
+
55
+ const focusableElements = getEnabledElements(wrapperRef === null || wrapperRef === void 0 ? void 0 : wrapperRef.current);
56
+
57
+ if (!focusableElements || (focusableElements === null || focusableElements === void 0 ? void 0 : focusableElements.length) === 0) {
58
+ return;
59
+ }
60
+
61
+ if (!((_wrapperRef$current = wrapperRef.current) !== null && _wrapperRef$current !== void 0 && _wrapperRef$current.contains(targetElement))) {
62
+ currentSelectedItemIndex.current = -1;
63
+ }
64
+
65
+ switch (event.key) {
66
+ case 'ArrowDown':
67
+ //If ArrowDown pressed
68
+ //If on last item
69
+ // Focus last item
70
+ //Else
71
+ // Focus next item
72
+ incrementIndex(focusableElements);
73
+ (_focusableElements$cu = focusableElements[currentSelectedItemIndex.current]) === null || _focusableElements$cu === void 0 ? void 0 : _focusableElements$cu.focus();
74
+ event.preventDefault();
75
+ break;
76
+
77
+ case 'ArrowUp':
78
+ //ArrowUP pressed
79
+ //If on First item
80
+ // Focus last item
81
+ //Else
82
+ // Focus previous item
83
+ decrementIndex(focusableElements);
84
+ (_focusableElements$cu2 = focusableElements[currentSelectedItemIndex.current]) === null || _focusableElements$cu2 === void 0 ? void 0 : _focusableElements$cu2.focus();
85
+ event.preventDefault();
86
+ break;
87
+ //ArrowLeft/Right on the menu should close the menus
88
+ //then logic to retain the focus can be handled in the parent components with KeydownHandlerContext
89
+
90
+ case 'ArrowLeft':
91
+ //Filter out the events from outside the menu
92
+ if (!targetElement.closest('.custom-key-handler-wrapper')) {
93
+ return;
94
+ }
95
+
96
+ handleClose(event);
97
+ keyDownHandlerContext === null || keyDownHandlerContext === void 0 ? void 0 : keyDownHandlerContext.handleArrowLeft();
98
+ break;
99
+
100
+ case 'ArrowRight':
101
+ //Filter out the events from outside the menu
102
+ if (!targetElement.closest('.custom-key-handler-wrapper')) {
103
+ return;
104
+ }
105
+
106
+ handleClose(event);
107
+ keyDownHandlerContext === null || keyDownHandlerContext === void 0 ? void 0 : keyDownHandlerContext.handleArrowRight();
108
+ break;
109
+
110
+ case 'Escape':
111
+ handleClose(event);
112
+ break;
113
+
114
+ default:
115
+ return;
116
+ }
117
+ };
118
+
119
+ document.addEventListener('keydown', handleKeyDown);
120
+ return () => {
121
+ document.removeEventListener('keydown', handleKeyDown);
122
+ };
123
+ }, [currentSelectedItemIndex, wrapperRef, handleClose, disableArrowKeyNavigation, keyDownHandlerContext, closeonTab]);
124
+ return /*#__PURE__*/React.createElement("div", {
125
+ className: "custom-key-handler-wrapper",
126
+ ref: wrapperRef
127
+ }, children);
128
+ };
129
+
130
+ function getFocusableElements(rootNode) {
131
+ if (!rootNode) {
132
+ return [];
133
+ }
134
+
135
+ const focusableModalElements = rootNode.querySelectorAll('a[href], button:not([disabled]), textarea, input, select, div[tabindex="-1"]') || [];
136
+ return Array.from(focusableModalElements);
137
+ }
138
+ /**
139
+ * This method filters out all the disabled menu items
140
+ */
141
+
142
+
143
+ function getEnabledElements(rootNode) {
144
+ const focusableElements = getFocusableElements(rootNode);
145
+ return focusableElements.filter(element => !element.querySelector('[role="button"][aria-disabled="true"]') && !element.querySelector('[role="menuitem"][aria-disabled="true"]'));
146
+ }