@atlaskit/editor-plugin-toolbar 2.1.3 → 2.1.5

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @atlaskit/editor-plugin-toolbar
2
2
 
3
+ ## 2.1.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [`bb498825fca37`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/bb498825fca37) -
8
+ [ux] [ED-29266] add ToolbarKeyboardNavigationProvider to selection toolbar with Alt+F10 shortcut
9
+ to focus
10
+ - Updated dependencies
11
+
12
+ ## 2.1.4
13
+
14
+ ### Patch Changes
15
+
16
+ - [`e24c73c66f022`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/e24c73c66f022) -
17
+ [ux] ED-29268 [SoftServ] Toolbar doesn’t move with text when text alignment change
18
+ - Updated dependencies
19
+
3
20
  ## 2.1.3
4
21
 
5
22
  ### Patch Changes
@@ -17,6 +17,9 @@
17
17
  "../src/**/examples.*"
18
18
  ],
19
19
  "references": [
20
+ {
21
+ "path": "../../../helpers/browser-apis/afm-cc/tsconfig.json"
22
+ },
20
23
  {
21
24
  "path": "../../editor-plugin-analytics/afm-cc/tsconfig.json"
22
25
  },
@@ -17,6 +17,9 @@
17
17
  "../src/**/examples.*"
18
18
  ],
19
19
  "references": [
20
+ {
21
+ "path": "../../editor-plugin-analytics/afm-dev-agents/tsconfig.json"
22
+ },
20
23
  {
21
24
  "path": "../../editor-plugin-connectivity/afm-dev-agents/tsconfig.json"
22
25
  },
@@ -17,6 +17,9 @@
17
17
  "../src/**/examples.*"
18
18
  ],
19
19
  "references": [
20
+ {
21
+ "path": "../../editor-plugin-analytics/afm-jira/tsconfig.json"
22
+ },
20
23
  {
21
24
  "path": "../../editor-plugin-connectivity/afm-jira/tsconfig.json"
22
25
  },
@@ -17,6 +17,9 @@
17
17
  "../src/**/examples.*"
18
18
  ],
19
19
  "references": [
20
+ {
21
+ "path": "../../editor-plugin-analytics/afm-passionfruit/tsconfig.json"
22
+ },
20
23
  {
21
24
  "path": "../../editor-plugin-connectivity/afm-passionfruit/tsconfig.json"
22
25
  },
@@ -17,6 +17,9 @@
17
17
  "../src/**/examples.*"
18
18
  ],
19
19
  "references": [
20
+ {
21
+ "path": "../../editor-plugin-analytics/afm-post-office/tsconfig.json"
22
+ },
20
23
  {
21
24
  "path": "../../editor-plugin-connectivity/afm-post-office/tsconfig.json"
22
25
  },
@@ -17,6 +17,9 @@
17
17
  "../src/**/examples.*"
18
18
  ],
19
19
  "references": [
20
+ {
21
+ "path": "../../editor-plugin-analytics/afm-rovo-extension/tsconfig.json"
22
+ },
20
23
  {
21
24
  "path": "../../editor-plugin-connectivity/afm-rovo-extension/tsconfig.json"
22
25
  },
@@ -17,6 +17,9 @@
17
17
  "../src/**/examples.*"
18
18
  ],
19
19
  "references": [
20
+ {
21
+ "path": "../../editor-plugin-analytics/afm-townsquare/tsconfig.json"
22
+ },
20
23
  {
21
24
  "path": "../../editor-plugin-connectivity/afm-townsquare/tsconfig.json"
22
25
  },
@@ -9,7 +9,10 @@ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/de
9
9
  var _react = _interopRequireDefault(require("react"));
10
10
  var _bindEventListener = require("bind-event-listener");
11
11
  var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
12
+ var _state = require("@atlaskit/editor-prosemirror/state");
13
+ var _utils = require("@atlaskit/editor-prosemirror/utils");
12
14
  var _editorToolbarModel = require("@atlaskit/editor-toolbar-model");
15
+ var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
13
16
  var _pluginKey = require("./pm-plugins/plugin-key");
14
17
  var _consts = require("./ui/consts");
15
18
  var _SelectionToolbar = require("./ui/SelectionToolbar");
@@ -17,6 +20,51 @@ var _toolbarComponents = require("./ui/toolbar-components");
17
20
  var _toolbar = require("./ui/utils/toolbar");
18
21
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
19
22
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
23
+ function getSelectedNode(editorState) {
24
+ var selection = editorState.selection;
25
+ if (selection instanceof _state.NodeSelection) {
26
+ return {
27
+ node: selection.node,
28
+ pos: selection.from,
29
+ nodeType: selection.node.type.name,
30
+ marks: selection.node.marks.map(function (mark) {
31
+ return "".concat(mark.type.name, ":").concat(JSON.stringify(mark.attrs));
32
+ })
33
+ };
34
+ }
35
+ var nodes = editorState.schema.nodes;
36
+ var selectedNode = (0, _utils.findSelectedNodeOfType)([nodes.paragraph, nodes.heading, nodes.blockquote, nodes.panel, nodes.codeBlock])(selection);
37
+ if (selectedNode) {
38
+ return {
39
+ node: selectedNode.node,
40
+ pos: selectedNode.pos,
41
+ nodeType: selectedNode.node.type.name,
42
+ marks: selectedNode.node.marks.map(function (mark) {
43
+ return "".concat(mark.type.name, ":").concat(JSON.stringify(mark.attrs));
44
+ })
45
+ };
46
+ }
47
+ var parentNode = (0, _utils.findParentNodeOfType)([nodes.paragraph, nodes.heading, nodes.blockquote, nodes.panel, nodes.listItem, nodes.taskItem])(selection);
48
+ if (parentNode) {
49
+ return {
50
+ node: parentNode.node,
51
+ pos: parentNode.pos,
52
+ nodeType: parentNode.node.type.name,
53
+ marks: parentNode.node.marks.map(function (mark) {
54
+ return "".concat(mark.type.name, ":").concat(JSON.stringify(mark.attrs));
55
+ })
56
+ };
57
+ }
58
+ var $pos = selection.$from;
59
+ return {
60
+ node: $pos.parent,
61
+ pos: $pos.pos,
62
+ nodeType: $pos.parent.type.name,
63
+ marks: $pos.marks().map(function (mark) {
64
+ return "".concat(mark.type.name, ":").concat(JSON.stringify(mark.attrs));
65
+ })
66
+ };
67
+ }
20
68
  var toolbarPlugin = exports.toolbarPlugin = function toolbarPlugin(_ref) {
21
69
  var api = _ref.api,
22
70
  _ref$config = _ref.config,
@@ -57,16 +105,28 @@ var toolbarPlugin = exports.toolbarPlugin = function toolbarPlugin(_ref) {
57
105
  return new _safePlugin.SafePlugin({
58
106
  key: _pluginKey.editorToolbarPluginKey,
59
107
  state: {
60
- init: function init() {
108
+ init: function init(_, editorState) {
61
109
  return {
62
- shouldShowToolbar: false
110
+ shouldShowToolbar: false,
111
+ selectedNode: getSelectedNode(editorState)
63
112
  };
64
113
  },
65
- apply: function apply(tr, pluginState) {
114
+ apply: function apply(tr, pluginState, _, newState) {
66
115
  var meta = tr.getMeta(_pluginKey.editorToolbarPluginKey);
67
- var newPluginState = pluginState;
116
+ var newPluginState = _objectSpread({}, pluginState);
117
+ if ((0, _expValEquals.expValEquals)('platform_editor_toolbar_aifc_patch_5', 'isEnabled', true)) {
118
+ var shouldUpdateNode = tr.docChanged || tr.selectionSet;
119
+ if (shouldUpdateNode) {
120
+ var newSelectedNode = getSelectedNode(newState);
121
+ var oldNode = pluginState.selectedNode;
122
+ var hasNodeChanged = !oldNode || !newSelectedNode || oldNode.nodeType !== newSelectedNode.nodeType || oldNode.pos !== newSelectedNode.pos || JSON.stringify(oldNode.marks) !== JSON.stringify(newSelectedNode.marks);
123
+ if (hasNodeChanged) {
124
+ newPluginState.selectedNode = newSelectedNode;
125
+ }
126
+ }
127
+ }
68
128
  if (meta) {
69
- return _objectSpread(_objectSpread({}, newPluginState), meta);
129
+ newPluginState = _objectSpread(_objectSpread({}, newPluginState), meta);
70
130
  }
71
131
  return newPluginState;
72
132
  }
@@ -6,8 +6,11 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.SelectionToolbar = void 0;
8
8
  var _react = _interopRequireWildcard(require("react"));
9
+ var _reactIntlNext = require("react-intl-next");
10
+ var _browserApis = require("@atlaskit/browser-apis");
9
11
  var _coreUtils = require("@atlaskit/editor-common/core-utils");
10
12
  var _hooks = require("@atlaskit/editor-common/hooks");
13
+ var _messages = require("@atlaskit/editor-common/messages");
11
14
  var _toolbar = require("@atlaskit/editor-common/toolbar");
12
15
  var _ui = require("@atlaskit/editor-common/ui");
13
16
  var _useSharedPluginStateSelector = require("@atlaskit/editor-common/use-shared-plugin-state-selector");
@@ -20,6 +23,7 @@ var _platformFeatureFlagsReact = require("@atlaskit/platform-feature-flags-react
20
23
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
21
24
  var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
22
25
  var _consts = require("../consts");
26
+ var _toolbar2 = require("../utils/toolbar");
23
27
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
24
28
  var isToolbarComponent = function isToolbarComponent(component) {
25
29
  return component.type === 'toolbar' && component.key === 'inline-text-toolbar';
@@ -28,11 +32,12 @@ var usePluginState = (0, _platformFeatureFlagsReact.conditionalHooksFactory)(fun
28
32
  return (0, _expValEquals.expValEquals)('platform_editor_toolbar_aifc_patch_3', 'isEnabled', true);
29
33
  }, function (api) {
30
34
  return (0, _hooks.useSharedPluginStateWithSelector)(api, ['connectivity', 'userPreferences', 'toolbar', 'selection', 'userIntent', 'editorViewMode'], function (state) {
31
- var _state$connectivitySt, _state$userPreference, _state$toolbarState, _state$selectionState, _state$userIntentStat, _state$editorViewMode;
35
+ var _state$connectivitySt, _state$userPreference, _state$toolbarState, _state$toolbarState2, _state$selectionState, _state$userIntentStat, _state$editorViewMode;
32
36
  return {
33
37
  connectivityStateMode: (_state$connectivitySt = state.connectivityState) === null || _state$connectivitySt === void 0 ? void 0 : _state$connectivitySt.mode,
34
38
  editorToolbarDockingPreference: (_state$userPreference = state.userPreferencesState) === null || _state$userPreference === void 0 || (_state$userPreference = _state$userPreference.preferences) === null || _state$userPreference === void 0 ? void 0 : _state$userPreference.toolbarDockingPosition,
35
39
  shouldShowToolbar: (_state$toolbarState = state.toolbarState) === null || _state$toolbarState === void 0 ? void 0 : _state$toolbarState.shouldShowToolbar,
40
+ selectedNode: (_state$toolbarState2 = state.toolbarState) === null || _state$toolbarState2 === void 0 ? void 0 : _state$toolbarState2.selectedNode,
36
41
  selection: (_state$selectionState = state.selectionState) === null || _state$selectionState === void 0 ? void 0 : _state$selectionState.selection,
37
42
  currentUserIntent: (_state$userIntentStat = state.userIntentState) === null || _state$userIntentStat === void 0 ? void 0 : _state$userIntentStat.currentUserIntent,
38
43
  editorViewMode: (_state$editorViewMode = state.editorViewModeState) === null || _state$editorViewMode === void 0 ? void 0 : _state$editorViewMode.mode
@@ -44,19 +49,22 @@ var usePluginState = (0, _platformFeatureFlagsReact.conditionalHooksFactory)(fun
44
49
  var currentUserIntent = (0, _useSharedPluginStateSelector.useSharedPluginStateSelector)(api, 'userIntent.currentUserIntent');
45
50
  var selection = (0, _useSharedPluginStateSelector.useSharedPluginStateSelector)(api, 'selection.selection');
46
51
  var _useSharedPluginState = (0, _hooks.useSharedPluginStateWithSelector)(api, ['toolbar'], function (state) {
47
- var _state$toolbarState2;
52
+ var _state$toolbarState3, _state$toolbarState4;
48
53
  return {
49
- shouldShowToolbar: (_state$toolbarState2 = state.toolbarState) === null || _state$toolbarState2 === void 0 ? void 0 : _state$toolbarState2.shouldShowToolbar
54
+ shouldShowToolbar: (_state$toolbarState3 = state.toolbarState) === null || _state$toolbarState3 === void 0 ? void 0 : _state$toolbarState3.shouldShowToolbar,
55
+ selectedNode: (_state$toolbarState4 = state.toolbarState) === null || _state$toolbarState4 === void 0 ? void 0 : _state$toolbarState4.selectedNode
50
56
  };
51
57
  }),
52
- shouldShowToolbar = _useSharedPluginState.shouldShowToolbar;
58
+ shouldShowToolbar = _useSharedPluginState.shouldShowToolbar,
59
+ selectedNode = _useSharedPluginState.selectedNode;
53
60
  return {
54
61
  connectivityStateMode: connectivityStateMode,
55
62
  editorToolbarDockingPreference: editorToolbarDockingPreference,
56
63
  currentUserIntent: currentUserIntent,
57
64
  shouldShowToolbar: shouldShowToolbar,
58
65
  selection: selection,
59
- editorViewMode: undefined
66
+ editorViewMode: undefined,
67
+ selectedNode: selectedNode
60
68
  };
61
69
  });
62
70
  var SelectionToolbar = exports.SelectionToolbar = function SelectionToolbar(_ref) {
@@ -72,10 +80,14 @@ var SelectionToolbar = exports.SelectionToolbar = function SelectionToolbar(_ref
72
80
  shouldShowToolbar = _usePluginState.shouldShowToolbar,
73
81
  editorViewMode = _usePluginState.editorViewMode,
74
82
  selection = _usePluginState.selection;
83
+ var intl = (0, _reactIntlNext.useIntl)();
75
84
  var components = api === null || api === void 0 || (_api$toolbar = api.toolbar) === null || _api$toolbar === void 0 ? void 0 : _api$toolbar.actions.getComponents();
76
85
  var toolbar = components === null || components === void 0 ? void 0 : components.find(function (component) {
77
86
  return isToolbarComponent(component);
78
87
  });
88
+ var keyboardNavigation = (0, _react.useMemo)(function () {
89
+ return getKeyboardNavigationConfig(editorView, intl, api);
90
+ }, [editorView, intl, api]);
79
91
  var isOffline = connectivityStateMode === 'offline';
80
92
  var isTextSelection = !editorView.state.selection.empty && editorView.state.selection instanceof _state.TextSelection;
81
93
  var isAllSelection = !editorView.state.selection.empty && editorView.state.selection instanceof _state.AllSelection && (0, _expValEquals.expValEquals)('platform_editor_toolbar_aifc_patch_2', 'isEnabled', true);
@@ -89,8 +101,7 @@ var SelectionToolbar = exports.SelectionToolbar = function SelectionToolbar(_ref
89
101
  if (isCellSelection && isEditorControlsEnabled) {
90
102
  return (0, _utils.calculateToolbarPositionOnCellSelection)(toolbarTitle)(editorView, position);
91
103
  }
92
- var calc = _utils.calculateToolbarPositionTrackHead;
93
- return calc(toolbarTitle)(editorView, position);
104
+ return (0, _utils.calculateToolbarPositionTrackHead)(toolbarTitle)(editorView, position);
94
105
  }, [editorView]);
95
106
  if ((0, _expValEquals.expValEquals)('platform_editor_toolbar_aifc_template_editor', 'isEnabled', true) && editorToolbarDockingPreference === 'top' && disableSelectionToolbarWhenPinned || !components || !toolbar) {
96
107
  return null;
@@ -115,7 +126,8 @@ var SelectionToolbar = exports.SelectionToolbar = function SelectionToolbar(_ref
115
126
  fireAnalyticsEvent: (0, _expValEquals.expValEquals)('platform_editor_toolbar_aifc_toolbar_analytic', 'isEnabled', true) ? function (payload) {
116
127
  var _api$analytics;
117
128
  api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.fireAnalyticsEvent(payload);
118
- } : undefined
129
+ } : undefined,
130
+ keyboardNavigation: (0, _expValEquals.expValEquals)('platform_editor_toolbar_aifc_patch_5', 'isEnabled', true) ? keyboardNavigation : undefined
119
131
  }, /*#__PURE__*/_react.default.createElement(_editorToolbarModel.ToolbarModelRenderer, {
120
132
  toolbar: toolbar,
121
133
  components: components,
@@ -157,4 +169,47 @@ var getDomRefFromSelection = function getDomRefFromSelection(view
157
169
  // dispatchAnalyticsEvent(payload);
158
170
  // }
159
171
  }
172
+ };
173
+ var getKeyboardNavigationConfig = function getKeyboardNavigationConfig(editorView, intl, api) {
174
+ if (!(editorView.dom instanceof HTMLElement)) {
175
+ return;
176
+ }
177
+ var toolbarSelector = "[data-testid='editor-floating-toolbar']";
178
+ return {
179
+ childComponentSelector: toolbarSelector,
180
+ dom: editorView.dom,
181
+ isShortcutToFocusToolbar: _toolbar2.isShortcutToFocusToolbar,
182
+ handleFocus: function handleFocus(event) {
183
+ var _getDocument, _filteredFocusableEle, _filteredFocusableEle2, _filteredFocusableEle3;
184
+ var toolbar = (_getDocument = (0, _browserApis.getDocument)()) === null || _getDocument === void 0 ? void 0 : _getDocument.querySelector(toolbarSelector);
185
+ if (!(toolbar instanceof HTMLElement)) {
186
+ return;
187
+ }
188
+ var filteredFocusableElements = (0, _toolbar2.getFocusableElements)(toolbar);
189
+ (_filteredFocusableEle = filteredFocusableElements[0]) === null || _filteredFocusableEle === void 0 || _filteredFocusableEle.focus();
190
+
191
+ // the button element removes the focus ring so this class adds it back
192
+ if (((_filteredFocusableEle2 = filteredFocusableElements[0]) === null || _filteredFocusableEle2 === void 0 ? void 0 : _filteredFocusableEle2.tagName) === 'BUTTON') {
193
+ filteredFocusableElements[0].classList.add('first-floating-toolbar-button');
194
+ }
195
+ (_filteredFocusableEle3 = filteredFocusableElements[0]) === null || _filteredFocusableEle3 === void 0 || _filteredFocusableEle3.scrollIntoView({
196
+ behavior: 'smooth',
197
+ block: 'center',
198
+ inline: 'nearest'
199
+ });
200
+ event.preventDefault();
201
+ event.stopPropagation();
202
+ },
203
+ handleEscape: function handleEscape(event) {
204
+ var isDropdownOpen = !!document.querySelector('[data-toolbar-component="menu-section"]');
205
+ if (isDropdownOpen) {
206
+ return;
207
+ }
208
+ api === null || api === void 0 || api.core.actions.focus();
209
+ event.preventDefault();
210
+ event.stopPropagation();
211
+ },
212
+ ariaControls: _ui.EDIT_AREA_ID,
213
+ ariaLabel: intl.formatMessage(_messages.fullPageMessages.toolbarLabel)
214
+ };
160
215
  };
@@ -25,7 +25,8 @@ var getToolbarComponents = exports.getToolbarComponents = function getToolbarCom
25
25
  var children = _ref.children;
26
26
  return /*#__PURE__*/_react.default.createElement(_editorToolbar.Toolbar, {
27
27
  label: _consts.SELECTION_TOOLBAR_LABEL,
28
- actionSubjectId: _analytics.ACTION_SUBJECT_ID.SELECTION_TOOLBAR
28
+ actionSubjectId: _analytics.ACTION_SUBJECT_ID.SELECTION_TOOLBAR,
29
+ testId: (0, _expValEquals.expValEquals)('platform_editor_toolbar_aifc_patch_5', 'isEnabled', true) ? 'editor-floating-toolbar' : undefined
29
30
  }, children);
30
31
  }
31
32
  }, {
@@ -3,11 +3,29 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.isEventInContainer = void 0;
6
+ exports.isShortcutToFocusToolbar = exports.isEventInContainer = exports.getFocusableElements = void 0;
7
7
  var isEventInContainer = exports.isEventInContainer = function isEventInContainer(event, containerSelector) {
8
8
  var target = event.target instanceof Element ? event.target : null;
9
9
  if (!target) {
10
10
  return false;
11
11
  }
12
12
  return !!target.closest(containerSelector);
13
+ };
14
+ var isShortcutToFocusToolbar = exports.isShortcutToFocusToolbar = function isShortcutToFocusToolbar(event) {
15
+ return event.altKey && event.key === 'F10';
16
+ };
17
+ var getFocusableElements = exports.getFocusableElements = function getFocusableElements(rootNode) {
18
+ if (!rootNode) {
19
+ return [];
20
+ }
21
+ var focusableElements = rootNode.querySelectorAll('a[href], button:not([disabled]), textarea, input, select, div[tabindex="-1"], div[tabindex="0"]') || [];
22
+ var focusableElementsArray = Array.from(focusableElements);
23
+
24
+ // filter out focusable elements from child components such as dropdown menus / popups
25
+ return focusableElementsArray.filter(function (elm) {
26
+ var style = window.getComputedStyle(elm);
27
+
28
+ // filter out invisible elements
29
+ return style.visibility !== 'hidden' && style.display !== 'none';
30
+ });
13
31
  };
@@ -1,12 +1,56 @@
1
1
  import React from 'react';
2
2
  import { bind } from 'bind-event-listener';
3
3
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
4
+ import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
5
+ import { findSelectedNodeOfType, findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
4
6
  import { createComponentRegistry } from '@atlaskit/editor-toolbar-model';
7
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
5
8
  import { editorToolbarPluginKey } from './pm-plugins/plugin-key';
6
9
  import { DEFAULT_POPUP_SELECTORS } from './ui/consts';
7
10
  import { SelectionToolbar } from './ui/SelectionToolbar';
8
11
  import { getToolbarComponents } from './ui/toolbar-components';
9
12
  import { isEventInContainer } from './ui/utils/toolbar';
13
+ function getSelectedNode(editorState) {
14
+ const {
15
+ selection
16
+ } = editorState;
17
+ if (selection instanceof NodeSelection) {
18
+ return {
19
+ node: selection.node,
20
+ pos: selection.from,
21
+ nodeType: selection.node.type.name,
22
+ marks: selection.node.marks.map(mark => `${mark.type.name}:${JSON.stringify(mark.attrs)}`)
23
+ };
24
+ }
25
+ const {
26
+ nodes
27
+ } = editorState.schema;
28
+ const selectedNode = findSelectedNodeOfType([nodes.paragraph, nodes.heading, nodes.blockquote, nodes.panel, nodes.codeBlock])(selection);
29
+ if (selectedNode) {
30
+ return {
31
+ node: selectedNode.node,
32
+ pos: selectedNode.pos,
33
+ nodeType: selectedNode.node.type.name,
34
+ marks: selectedNode.node.marks.map(mark => `${mark.type.name}:${JSON.stringify(mark.attrs)}`)
35
+ };
36
+ }
37
+ const parentNode = findParentNodeOfType([nodes.paragraph, nodes.heading, nodes.blockquote, nodes.panel, nodes.listItem, nodes.taskItem])(selection);
38
+ if (parentNode) {
39
+ return {
40
+ node: parentNode.node,
41
+ pos: parentNode.pos,
42
+ nodeType: parentNode.node.type.name,
43
+ marks: parentNode.node.marks.map(mark => `${mark.type.name}:${JSON.stringify(mark.attrs)}`)
44
+ };
45
+ }
46
+ const $pos = selection.$from;
47
+ return {
48
+ node: $pos.parent,
49
+ pos: $pos.pos,
50
+ nodeType: $pos.parent.type.name,
51
+ marks: $pos.marks().map(mark => `${mark.type.name}:${JSON.stringify(mark.attrs)}`)
52
+ };
53
+ }
10
54
  export const toolbarPlugin = ({
11
55
  api,
12
56
  config = {
@@ -48,16 +92,30 @@ export const toolbarPlugin = ({
48
92
  return new SafePlugin({
49
93
  key: editorToolbarPluginKey,
50
94
  state: {
51
- init() {
95
+ init(_, editorState) {
52
96
  return {
53
- shouldShowToolbar: false
97
+ shouldShowToolbar: false,
98
+ selectedNode: getSelectedNode(editorState)
54
99
  };
55
100
  },
56
- apply(tr, pluginState) {
101
+ apply(tr, pluginState, _, newState) {
57
102
  const meta = tr.getMeta(editorToolbarPluginKey);
58
- const newPluginState = pluginState;
103
+ let newPluginState = {
104
+ ...pluginState
105
+ };
106
+ if (expValEquals('platform_editor_toolbar_aifc_patch_5', 'isEnabled', true)) {
107
+ const shouldUpdateNode = tr.docChanged || tr.selectionSet;
108
+ if (shouldUpdateNode) {
109
+ const newSelectedNode = getSelectedNode(newState);
110
+ const oldNode = pluginState.selectedNode;
111
+ const hasNodeChanged = !oldNode || !newSelectedNode || oldNode.nodeType !== newSelectedNode.nodeType || oldNode.pos !== newSelectedNode.pos || JSON.stringify(oldNode.marks) !== JSON.stringify(newSelectedNode.marks);
112
+ if (hasNodeChanged) {
113
+ newPluginState.selectedNode = newSelectedNode;
114
+ }
115
+ }
116
+ }
59
117
  if (meta) {
60
- return {
118
+ newPluginState = {
61
119
  ...newPluginState,
62
120
  ...meta
63
121
  };
@@ -1,8 +1,11 @@
1
- import React, { useCallback } from 'react';
1
+ import React, { useCallback, useMemo } from 'react';
2
+ import { useIntl } from 'react-intl-next';
3
+ import { getDocument } from '@atlaskit/browser-apis';
2
4
  import { isSSR } from '@atlaskit/editor-common/core-utils';
3
5
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
6
+ import { fullPageMessages } from '@atlaskit/editor-common/messages';
4
7
  import { EditorToolbarProvider, EditorToolbarUIProvider } from '@atlaskit/editor-common/toolbar';
5
- import { Popup } from '@atlaskit/editor-common/ui';
8
+ import { Popup, EDIT_AREA_ID } from '@atlaskit/editor-common/ui';
6
9
  import { useSharedPluginStateSelector } from '@atlaskit/editor-common/use-shared-plugin-state-selector';
7
10
  import { calculateToolbarPositionTrackHead, calculateToolbarPositionOnCellSelection } from '@atlaskit/editor-common/utils';
8
11
  import { AllSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
@@ -13,16 +16,18 @@ import { conditionalHooksFactory } from '@atlaskit/platform-feature-flags-react'
13
16
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
14
17
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
15
18
  import { SELECTION_TOOLBAR_LABEL } from '../consts';
19
+ import { getFocusableElements, isShortcutToFocusToolbar } from '../utils/toolbar';
16
20
  const isToolbarComponent = component => {
17
21
  return component.type === 'toolbar' && component.key === 'inline-text-toolbar';
18
22
  };
19
23
  const usePluginState = conditionalHooksFactory(() => expValEquals('platform_editor_toolbar_aifc_patch_3', 'isEnabled', true), api => {
20
24
  return useSharedPluginStateWithSelector(api, ['connectivity', 'userPreferences', 'toolbar', 'selection', 'userIntent', 'editorViewMode'], state => {
21
- var _state$connectivitySt, _state$userPreference, _state$userPreference2, _state$toolbarState, _state$selectionState, _state$userIntentStat, _state$editorViewMode;
25
+ var _state$connectivitySt, _state$userPreference, _state$userPreference2, _state$toolbarState, _state$toolbarState2, _state$selectionState, _state$userIntentStat, _state$editorViewMode;
22
26
  return {
23
27
  connectivityStateMode: (_state$connectivitySt = state.connectivityState) === null || _state$connectivitySt === void 0 ? void 0 : _state$connectivitySt.mode,
24
28
  editorToolbarDockingPreference: (_state$userPreference = state.userPreferencesState) === null || _state$userPreference === void 0 ? void 0 : (_state$userPreference2 = _state$userPreference.preferences) === null || _state$userPreference2 === void 0 ? void 0 : _state$userPreference2.toolbarDockingPosition,
25
29
  shouldShowToolbar: (_state$toolbarState = state.toolbarState) === null || _state$toolbarState === void 0 ? void 0 : _state$toolbarState.shouldShowToolbar,
30
+ selectedNode: (_state$toolbarState2 = state.toolbarState) === null || _state$toolbarState2 === void 0 ? void 0 : _state$toolbarState2.selectedNode,
26
31
  selection: (_state$selectionState = state.selectionState) === null || _state$selectionState === void 0 ? void 0 : _state$selectionState.selection,
27
32
  currentUserIntent: (_state$userIntentStat = state.userIntentState) === null || _state$userIntentStat === void 0 ? void 0 : _state$userIntentStat.currentUserIntent,
28
33
  editorViewMode: (_state$editorViewMode = state.editorViewModeState) === null || _state$editorViewMode === void 0 ? void 0 : _state$editorViewMode.mode
@@ -34,11 +39,13 @@ const usePluginState = conditionalHooksFactory(() => expValEquals('platform_edit
34
39
  const currentUserIntent = useSharedPluginStateSelector(api, 'userIntent.currentUserIntent');
35
40
  const selection = useSharedPluginStateSelector(api, 'selection.selection');
36
41
  const {
37
- shouldShowToolbar
42
+ shouldShowToolbar,
43
+ selectedNode
38
44
  } = useSharedPluginStateWithSelector(api, ['toolbar'], state => {
39
- var _state$toolbarState2;
45
+ var _state$toolbarState3, _state$toolbarState4;
40
46
  return {
41
- shouldShowToolbar: (_state$toolbarState2 = state.toolbarState) === null || _state$toolbarState2 === void 0 ? void 0 : _state$toolbarState2.shouldShowToolbar
47
+ shouldShowToolbar: (_state$toolbarState3 = state.toolbarState) === null || _state$toolbarState3 === void 0 ? void 0 : _state$toolbarState3.shouldShowToolbar,
48
+ selectedNode: (_state$toolbarState4 = state.toolbarState) === null || _state$toolbarState4 === void 0 ? void 0 : _state$toolbarState4.selectedNode
42
49
  };
43
50
  });
44
51
  return {
@@ -47,7 +54,8 @@ const usePluginState = conditionalHooksFactory(() => expValEquals('platform_edit
47
54
  currentUserIntent,
48
55
  shouldShowToolbar,
49
56
  selection,
50
- editorViewMode: undefined
57
+ editorViewMode: undefined,
58
+ selectedNode
51
59
  };
52
60
  });
53
61
  export const SelectionToolbar = ({
@@ -66,8 +74,12 @@ export const SelectionToolbar = ({
66
74
  // @ts-expect-error
67
75
  selection
68
76
  } = usePluginState(api);
77
+ const intl = useIntl();
69
78
  const components = api === null || api === void 0 ? void 0 : (_api$toolbar = api.toolbar) === null || _api$toolbar === void 0 ? void 0 : _api$toolbar.actions.getComponents();
70
79
  const toolbar = components === null || components === void 0 ? void 0 : components.find(component => isToolbarComponent(component));
80
+ const keyboardNavigation = useMemo(() => {
81
+ return getKeyboardNavigationConfig(editorView, intl, api);
82
+ }, [editorView, intl, api]);
71
83
  const isOffline = connectivityStateMode === 'offline';
72
84
  const isTextSelection = !editorView.state.selection.empty && editorView.state.selection instanceof TextSelection;
73
85
  const isAllSelection = !editorView.state.selection.empty && editorView.state.selection instanceof AllSelection && expValEquals('platform_editor_toolbar_aifc_patch_2', 'isEnabled', true);
@@ -81,8 +93,7 @@ export const SelectionToolbar = ({
81
93
  if (isCellSelection && isEditorControlsEnabled) {
82
94
  return calculateToolbarPositionOnCellSelection(toolbarTitle)(editorView, position);
83
95
  }
84
- const calc = calculateToolbarPositionTrackHead;
85
- return calc(toolbarTitle)(editorView, position);
96
+ return calculateToolbarPositionTrackHead(toolbarTitle)(editorView, position);
86
97
  }, [editorView]);
87
98
  if (expValEquals('platform_editor_toolbar_aifc_template_editor', 'isEnabled', true) && editorToolbarDockingPreference === 'top' && disableSelectionToolbarWhenPinned || !components || !toolbar) {
88
99
  return null;
@@ -107,7 +118,8 @@ export const SelectionToolbar = ({
107
118
  fireAnalyticsEvent: expValEquals('platform_editor_toolbar_aifc_toolbar_analytic', 'isEnabled', true) ? payload => {
108
119
  var _api$analytics;
109
120
  api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.fireAnalyticsEvent(payload);
110
- } : undefined
121
+ } : undefined,
122
+ keyboardNavigation: expValEquals('platform_editor_toolbar_aifc_patch_5', 'isEnabled', true) ? keyboardNavigation : undefined
111
123
  }, /*#__PURE__*/React.createElement(ToolbarModelRenderer, {
112
124
  toolbar: toolbar,
113
125
  components: components,
@@ -149,4 +161,47 @@ const getDomRefFromSelection = (view
149
161
  // dispatchAnalyticsEvent(payload);
150
162
  // }
151
163
  }
164
+ };
165
+ const getKeyboardNavigationConfig = (editorView, intl, api) => {
166
+ if (!(editorView.dom instanceof HTMLElement)) {
167
+ return;
168
+ }
169
+ const toolbarSelector = "[data-testid='editor-floating-toolbar']";
170
+ return {
171
+ childComponentSelector: toolbarSelector,
172
+ dom: editorView.dom,
173
+ isShortcutToFocusToolbar: isShortcutToFocusToolbar,
174
+ handleFocus: event => {
175
+ var _getDocument, _filteredFocusableEle, _filteredFocusableEle2, _filteredFocusableEle3;
176
+ const toolbar = (_getDocument = getDocument()) === null || _getDocument === void 0 ? void 0 : _getDocument.querySelector(toolbarSelector);
177
+ if (!(toolbar instanceof HTMLElement)) {
178
+ return;
179
+ }
180
+ const filteredFocusableElements = getFocusableElements(toolbar);
181
+ (_filteredFocusableEle = filteredFocusableElements[0]) === null || _filteredFocusableEle === void 0 ? void 0 : _filteredFocusableEle.focus();
182
+
183
+ // the button element removes the focus ring so this class adds it back
184
+ if (((_filteredFocusableEle2 = filteredFocusableElements[0]) === null || _filteredFocusableEle2 === void 0 ? void 0 : _filteredFocusableEle2.tagName) === 'BUTTON') {
185
+ filteredFocusableElements[0].classList.add('first-floating-toolbar-button');
186
+ }
187
+ (_filteredFocusableEle3 = filteredFocusableElements[0]) === null || _filteredFocusableEle3 === void 0 ? void 0 : _filteredFocusableEle3.scrollIntoView({
188
+ behavior: 'smooth',
189
+ block: 'center',
190
+ inline: 'nearest'
191
+ });
192
+ event.preventDefault();
193
+ event.stopPropagation();
194
+ },
195
+ handleEscape: event => {
196
+ const isDropdownOpen = !!document.querySelector('[data-toolbar-component="menu-section"]');
197
+ if (isDropdownOpen) {
198
+ return;
199
+ }
200
+ api === null || api === void 0 ? void 0 : api.core.actions.focus();
201
+ event.preventDefault();
202
+ event.stopPropagation();
203
+ },
204
+ ariaControls: EDIT_AREA_ID,
205
+ ariaLabel: intl.formatMessage(fullPageMessages.toolbarLabel)
206
+ };
152
207
  };
@@ -18,7 +18,8 @@ export const getToolbarComponents = (api, disableSelectionToolbar) => {
18
18
  }) => {
19
19
  return /*#__PURE__*/React.createElement(Toolbar, {
20
20
  label: SELECTION_TOOLBAR_LABEL,
21
- actionSubjectId: ACTION_SUBJECT_ID.SELECTION_TOOLBAR
21
+ actionSubjectId: ACTION_SUBJECT_ID.SELECTION_TOOLBAR,
22
+ testId: expValEquals('platform_editor_toolbar_aifc_patch_5', 'isEnabled', true) ? 'editor-floating-toolbar' : undefined
22
23
  }, children);
23
24
  }
24
25
  }, {
@@ -4,4 +4,22 @@ export const isEventInContainer = (event, containerSelector) => {
4
4
  return false;
5
5
  }
6
6
  return !!target.closest(containerSelector);
7
+ };
8
+ export const isShortcutToFocusToolbar = event => {
9
+ return event.altKey && event.key === 'F10';
10
+ };
11
+ export const getFocusableElements = rootNode => {
12
+ if (!rootNode) {
13
+ return [];
14
+ }
15
+ const focusableElements = rootNode.querySelectorAll('a[href], button:not([disabled]), textarea, input, select, div[tabindex="-1"], div[tabindex="0"]') || [];
16
+ const focusableElementsArray = Array.from(focusableElements);
17
+
18
+ // filter out focusable elements from child components such as dropdown menus / popups
19
+ return focusableElementsArray.filter(elm => {
20
+ const style = window.getComputedStyle(elm);
21
+
22
+ // filter out invisible elements
23
+ return style.visibility !== 'hidden' && style.display !== 'none';
24
+ });
7
25
  };
@@ -4,12 +4,60 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
4
4
  import React from 'react';
5
5
  import { bind } from 'bind-event-listener';
6
6
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
7
+ import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
8
+ import { findSelectedNodeOfType, findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
7
9
  import { createComponentRegistry } from '@atlaskit/editor-toolbar-model';
10
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
8
11
  import { editorToolbarPluginKey } from './pm-plugins/plugin-key';
9
12
  import { DEFAULT_POPUP_SELECTORS } from './ui/consts';
10
13
  import { SelectionToolbar } from './ui/SelectionToolbar';
11
14
  import { getToolbarComponents } from './ui/toolbar-components';
12
15
  import { isEventInContainer } from './ui/utils/toolbar';
16
+ function getSelectedNode(editorState) {
17
+ var selection = editorState.selection;
18
+ if (selection instanceof NodeSelection) {
19
+ return {
20
+ node: selection.node,
21
+ pos: selection.from,
22
+ nodeType: selection.node.type.name,
23
+ marks: selection.node.marks.map(function (mark) {
24
+ return "".concat(mark.type.name, ":").concat(JSON.stringify(mark.attrs));
25
+ })
26
+ };
27
+ }
28
+ var nodes = editorState.schema.nodes;
29
+ var selectedNode = findSelectedNodeOfType([nodes.paragraph, nodes.heading, nodes.blockquote, nodes.panel, nodes.codeBlock])(selection);
30
+ if (selectedNode) {
31
+ return {
32
+ node: selectedNode.node,
33
+ pos: selectedNode.pos,
34
+ nodeType: selectedNode.node.type.name,
35
+ marks: selectedNode.node.marks.map(function (mark) {
36
+ return "".concat(mark.type.name, ":").concat(JSON.stringify(mark.attrs));
37
+ })
38
+ };
39
+ }
40
+ var parentNode = findParentNodeOfType([nodes.paragraph, nodes.heading, nodes.blockquote, nodes.panel, nodes.listItem, nodes.taskItem])(selection);
41
+ if (parentNode) {
42
+ return {
43
+ node: parentNode.node,
44
+ pos: parentNode.pos,
45
+ nodeType: parentNode.node.type.name,
46
+ marks: parentNode.node.marks.map(function (mark) {
47
+ return "".concat(mark.type.name, ":").concat(JSON.stringify(mark.attrs));
48
+ })
49
+ };
50
+ }
51
+ var $pos = selection.$from;
52
+ return {
53
+ node: $pos.parent,
54
+ pos: $pos.pos,
55
+ nodeType: $pos.parent.type.name,
56
+ marks: $pos.marks().map(function (mark) {
57
+ return "".concat(mark.type.name, ":").concat(JSON.stringify(mark.attrs));
58
+ })
59
+ };
60
+ }
13
61
  export var toolbarPlugin = function toolbarPlugin(_ref) {
14
62
  var api = _ref.api,
15
63
  _ref$config = _ref.config,
@@ -50,16 +98,28 @@ export var toolbarPlugin = function toolbarPlugin(_ref) {
50
98
  return new SafePlugin({
51
99
  key: editorToolbarPluginKey,
52
100
  state: {
53
- init: function init() {
101
+ init: function init(_, editorState) {
54
102
  return {
55
- shouldShowToolbar: false
103
+ shouldShowToolbar: false,
104
+ selectedNode: getSelectedNode(editorState)
56
105
  };
57
106
  },
58
- apply: function apply(tr, pluginState) {
107
+ apply: function apply(tr, pluginState, _, newState) {
59
108
  var meta = tr.getMeta(editorToolbarPluginKey);
60
- var newPluginState = pluginState;
109
+ var newPluginState = _objectSpread({}, pluginState);
110
+ if (expValEquals('platform_editor_toolbar_aifc_patch_5', 'isEnabled', true)) {
111
+ var shouldUpdateNode = tr.docChanged || tr.selectionSet;
112
+ if (shouldUpdateNode) {
113
+ var newSelectedNode = getSelectedNode(newState);
114
+ var oldNode = pluginState.selectedNode;
115
+ var hasNodeChanged = !oldNode || !newSelectedNode || oldNode.nodeType !== newSelectedNode.nodeType || oldNode.pos !== newSelectedNode.pos || JSON.stringify(oldNode.marks) !== JSON.stringify(newSelectedNode.marks);
116
+ if (hasNodeChanged) {
117
+ newPluginState.selectedNode = newSelectedNode;
118
+ }
119
+ }
120
+ }
61
121
  if (meta) {
62
- return _objectSpread(_objectSpread({}, newPluginState), meta);
122
+ newPluginState = _objectSpread(_objectSpread({}, newPluginState), meta);
63
123
  }
64
124
  return newPluginState;
65
125
  }
@@ -1,8 +1,11 @@
1
- import React, { useCallback } from 'react';
1
+ import React, { useCallback, useMemo } from 'react';
2
+ import { useIntl } from 'react-intl-next';
3
+ import { getDocument } from '@atlaskit/browser-apis';
2
4
  import { isSSR } from '@atlaskit/editor-common/core-utils';
3
5
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
6
+ import { fullPageMessages } from '@atlaskit/editor-common/messages';
4
7
  import { EditorToolbarProvider, EditorToolbarUIProvider } from '@atlaskit/editor-common/toolbar';
5
- import { Popup } from '@atlaskit/editor-common/ui';
8
+ import { Popup, EDIT_AREA_ID } from '@atlaskit/editor-common/ui';
6
9
  import { useSharedPluginStateSelector } from '@atlaskit/editor-common/use-shared-plugin-state-selector';
7
10
  import { calculateToolbarPositionTrackHead, calculateToolbarPositionOnCellSelection } from '@atlaskit/editor-common/utils';
8
11
  import { AllSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
@@ -13,6 +16,7 @@ import { conditionalHooksFactory } from '@atlaskit/platform-feature-flags-react'
13
16
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
14
17
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
15
18
  import { SELECTION_TOOLBAR_LABEL } from '../consts';
19
+ import { getFocusableElements, isShortcutToFocusToolbar } from '../utils/toolbar';
16
20
  var isToolbarComponent = function isToolbarComponent(component) {
17
21
  return component.type === 'toolbar' && component.key === 'inline-text-toolbar';
18
22
  };
@@ -20,11 +24,12 @@ var usePluginState = conditionalHooksFactory(function () {
20
24
  return expValEquals('platform_editor_toolbar_aifc_patch_3', 'isEnabled', true);
21
25
  }, function (api) {
22
26
  return useSharedPluginStateWithSelector(api, ['connectivity', 'userPreferences', 'toolbar', 'selection', 'userIntent', 'editorViewMode'], function (state) {
23
- var _state$connectivitySt, _state$userPreference, _state$toolbarState, _state$selectionState, _state$userIntentStat, _state$editorViewMode;
27
+ var _state$connectivitySt, _state$userPreference, _state$toolbarState, _state$toolbarState2, _state$selectionState, _state$userIntentStat, _state$editorViewMode;
24
28
  return {
25
29
  connectivityStateMode: (_state$connectivitySt = state.connectivityState) === null || _state$connectivitySt === void 0 ? void 0 : _state$connectivitySt.mode,
26
30
  editorToolbarDockingPreference: (_state$userPreference = state.userPreferencesState) === null || _state$userPreference === void 0 || (_state$userPreference = _state$userPreference.preferences) === null || _state$userPreference === void 0 ? void 0 : _state$userPreference.toolbarDockingPosition,
27
31
  shouldShowToolbar: (_state$toolbarState = state.toolbarState) === null || _state$toolbarState === void 0 ? void 0 : _state$toolbarState.shouldShowToolbar,
32
+ selectedNode: (_state$toolbarState2 = state.toolbarState) === null || _state$toolbarState2 === void 0 ? void 0 : _state$toolbarState2.selectedNode,
28
33
  selection: (_state$selectionState = state.selectionState) === null || _state$selectionState === void 0 ? void 0 : _state$selectionState.selection,
29
34
  currentUserIntent: (_state$userIntentStat = state.userIntentState) === null || _state$userIntentStat === void 0 ? void 0 : _state$userIntentStat.currentUserIntent,
30
35
  editorViewMode: (_state$editorViewMode = state.editorViewModeState) === null || _state$editorViewMode === void 0 ? void 0 : _state$editorViewMode.mode
@@ -36,19 +41,22 @@ var usePluginState = conditionalHooksFactory(function () {
36
41
  var currentUserIntent = useSharedPluginStateSelector(api, 'userIntent.currentUserIntent');
37
42
  var selection = useSharedPluginStateSelector(api, 'selection.selection');
38
43
  var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['toolbar'], function (state) {
39
- var _state$toolbarState2;
44
+ var _state$toolbarState3, _state$toolbarState4;
40
45
  return {
41
- shouldShowToolbar: (_state$toolbarState2 = state.toolbarState) === null || _state$toolbarState2 === void 0 ? void 0 : _state$toolbarState2.shouldShowToolbar
46
+ shouldShowToolbar: (_state$toolbarState3 = state.toolbarState) === null || _state$toolbarState3 === void 0 ? void 0 : _state$toolbarState3.shouldShowToolbar,
47
+ selectedNode: (_state$toolbarState4 = state.toolbarState) === null || _state$toolbarState4 === void 0 ? void 0 : _state$toolbarState4.selectedNode
42
48
  };
43
49
  }),
44
- shouldShowToolbar = _useSharedPluginState.shouldShowToolbar;
50
+ shouldShowToolbar = _useSharedPluginState.shouldShowToolbar,
51
+ selectedNode = _useSharedPluginState.selectedNode;
45
52
  return {
46
53
  connectivityStateMode: connectivityStateMode,
47
54
  editorToolbarDockingPreference: editorToolbarDockingPreference,
48
55
  currentUserIntent: currentUserIntent,
49
56
  shouldShowToolbar: shouldShowToolbar,
50
57
  selection: selection,
51
- editorViewMode: undefined
58
+ editorViewMode: undefined,
59
+ selectedNode: selectedNode
52
60
  };
53
61
  });
54
62
  export var SelectionToolbar = function SelectionToolbar(_ref) {
@@ -64,10 +72,14 @@ export var SelectionToolbar = function SelectionToolbar(_ref) {
64
72
  shouldShowToolbar = _usePluginState.shouldShowToolbar,
65
73
  editorViewMode = _usePluginState.editorViewMode,
66
74
  selection = _usePluginState.selection;
75
+ var intl = useIntl();
67
76
  var components = api === null || api === void 0 || (_api$toolbar = api.toolbar) === null || _api$toolbar === void 0 ? void 0 : _api$toolbar.actions.getComponents();
68
77
  var toolbar = components === null || components === void 0 ? void 0 : components.find(function (component) {
69
78
  return isToolbarComponent(component);
70
79
  });
80
+ var keyboardNavigation = useMemo(function () {
81
+ return getKeyboardNavigationConfig(editorView, intl, api);
82
+ }, [editorView, intl, api]);
71
83
  var isOffline = connectivityStateMode === 'offline';
72
84
  var isTextSelection = !editorView.state.selection.empty && editorView.state.selection instanceof TextSelection;
73
85
  var isAllSelection = !editorView.state.selection.empty && editorView.state.selection instanceof AllSelection && expValEquals('platform_editor_toolbar_aifc_patch_2', 'isEnabled', true);
@@ -81,8 +93,7 @@ export var SelectionToolbar = function SelectionToolbar(_ref) {
81
93
  if (isCellSelection && isEditorControlsEnabled) {
82
94
  return calculateToolbarPositionOnCellSelection(toolbarTitle)(editorView, position);
83
95
  }
84
- var calc = calculateToolbarPositionTrackHead;
85
- return calc(toolbarTitle)(editorView, position);
96
+ return calculateToolbarPositionTrackHead(toolbarTitle)(editorView, position);
86
97
  }, [editorView]);
87
98
  if (expValEquals('platform_editor_toolbar_aifc_template_editor', 'isEnabled', true) && editorToolbarDockingPreference === 'top' && disableSelectionToolbarWhenPinned || !components || !toolbar) {
88
99
  return null;
@@ -107,7 +118,8 @@ export var SelectionToolbar = function SelectionToolbar(_ref) {
107
118
  fireAnalyticsEvent: expValEquals('platform_editor_toolbar_aifc_toolbar_analytic', 'isEnabled', true) ? function (payload) {
108
119
  var _api$analytics;
109
120
  api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.fireAnalyticsEvent(payload);
110
- } : undefined
121
+ } : undefined,
122
+ keyboardNavigation: expValEquals('platform_editor_toolbar_aifc_patch_5', 'isEnabled', true) ? keyboardNavigation : undefined
111
123
  }, /*#__PURE__*/React.createElement(ToolbarModelRenderer, {
112
124
  toolbar: toolbar,
113
125
  components: components,
@@ -149,4 +161,47 @@ var getDomRefFromSelection = function getDomRefFromSelection(view
149
161
  // dispatchAnalyticsEvent(payload);
150
162
  // }
151
163
  }
164
+ };
165
+ var getKeyboardNavigationConfig = function getKeyboardNavigationConfig(editorView, intl, api) {
166
+ if (!(editorView.dom instanceof HTMLElement)) {
167
+ return;
168
+ }
169
+ var toolbarSelector = "[data-testid='editor-floating-toolbar']";
170
+ return {
171
+ childComponentSelector: toolbarSelector,
172
+ dom: editorView.dom,
173
+ isShortcutToFocusToolbar: isShortcutToFocusToolbar,
174
+ handleFocus: function handleFocus(event) {
175
+ var _getDocument, _filteredFocusableEle, _filteredFocusableEle2, _filteredFocusableEle3;
176
+ var toolbar = (_getDocument = getDocument()) === null || _getDocument === void 0 ? void 0 : _getDocument.querySelector(toolbarSelector);
177
+ if (!(toolbar instanceof HTMLElement)) {
178
+ return;
179
+ }
180
+ var filteredFocusableElements = getFocusableElements(toolbar);
181
+ (_filteredFocusableEle = filteredFocusableElements[0]) === null || _filteredFocusableEle === void 0 || _filteredFocusableEle.focus();
182
+
183
+ // the button element removes the focus ring so this class adds it back
184
+ if (((_filteredFocusableEle2 = filteredFocusableElements[0]) === null || _filteredFocusableEle2 === void 0 ? void 0 : _filteredFocusableEle2.tagName) === 'BUTTON') {
185
+ filteredFocusableElements[0].classList.add('first-floating-toolbar-button');
186
+ }
187
+ (_filteredFocusableEle3 = filteredFocusableElements[0]) === null || _filteredFocusableEle3 === void 0 || _filteredFocusableEle3.scrollIntoView({
188
+ behavior: 'smooth',
189
+ block: 'center',
190
+ inline: 'nearest'
191
+ });
192
+ event.preventDefault();
193
+ event.stopPropagation();
194
+ },
195
+ handleEscape: function handleEscape(event) {
196
+ var isDropdownOpen = !!document.querySelector('[data-toolbar-component="menu-section"]');
197
+ if (isDropdownOpen) {
198
+ return;
199
+ }
200
+ api === null || api === void 0 || api.core.actions.focus();
201
+ event.preventDefault();
202
+ event.stopPropagation();
203
+ },
204
+ ariaControls: EDIT_AREA_ID,
205
+ ariaLabel: intl.formatMessage(fullPageMessages.toolbarLabel)
206
+ };
152
207
  };
@@ -18,7 +18,8 @@ export var getToolbarComponents = function getToolbarComponents(api, disableSele
18
18
  var children = _ref.children;
19
19
  return /*#__PURE__*/React.createElement(Toolbar, {
20
20
  label: SELECTION_TOOLBAR_LABEL,
21
- actionSubjectId: ACTION_SUBJECT_ID.SELECTION_TOOLBAR
21
+ actionSubjectId: ACTION_SUBJECT_ID.SELECTION_TOOLBAR,
22
+ testId: expValEquals('platform_editor_toolbar_aifc_patch_5', 'isEnabled', true) ? 'editor-floating-toolbar' : undefined
22
23
  }, children);
23
24
  }
24
25
  }, {
@@ -4,4 +4,22 @@ export var isEventInContainer = function isEventInContainer(event, containerSele
4
4
  return false;
5
5
  }
6
6
  return !!target.closest(containerSelector);
7
+ };
8
+ export var isShortcutToFocusToolbar = function isShortcutToFocusToolbar(event) {
9
+ return event.altKey && event.key === 'F10';
10
+ };
11
+ export var getFocusableElements = function getFocusableElements(rootNode) {
12
+ if (!rootNode) {
13
+ return [];
14
+ }
15
+ var focusableElements = rootNode.querySelectorAll('a[href], button:not([disabled]), textarea, input, select, div[tabindex="-1"], div[tabindex="0"]') || [];
16
+ var focusableElementsArray = Array.from(focusableElements);
17
+
18
+ // filter out focusable elements from child components such as dropdown menus / popups
19
+ return focusableElementsArray.filter(function (elm) {
20
+ var style = window.getComputedStyle(elm);
21
+
22
+ // filter out invisible elements
23
+ return style.visibility !== 'hidden' && style.display !== 'none';
24
+ });
7
25
  };
@@ -1,5 +1,2 @@
1
1
  import type { ToolbarPlugin } from './toolbarPluginType';
2
- export type EditorToolbarPluginState = {
3
- shouldShowToolbar: boolean;
4
- };
5
2
  export declare const toolbarPlugin: ToolbarPlugin;
@@ -5,8 +5,18 @@ import type { EditorViewModePlugin } from '@atlaskit/editor-plugin-editor-viewmo
5
5
  import type { SelectionPlugin } from '@atlaskit/editor-plugin-selection';
6
6
  import type { UserIntentPlugin } from '@atlaskit/editor-plugin-user-intent';
7
7
  import type { UserPreferencesPlugin } from '@atlaskit/editor-plugin-user-preferences';
8
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
8
9
  import type { RegisterComponent } from '@atlaskit/editor-toolbar-model';
9
10
  import type { RegisterComponentsAction, ToolbarPluginOptions } from './types';
11
+ export type EditorToolbarPluginState = {
12
+ selectedNode?: {
13
+ marks: string[];
14
+ node: PMNode;
15
+ nodeType: string;
16
+ pos: number;
17
+ };
18
+ shouldShowToolbar: boolean;
19
+ };
10
20
  export type ToolbarPlugin = NextEditorPlugin<'toolbar', {
11
21
  actions: {
12
22
  getComponents: () => Array<RegisterComponent>;
@@ -21,7 +31,5 @@ export type ToolbarPlugin = NextEditorPlugin<'toolbar', {
21
31
  OptionalPlugin<AnalyticsPlugin>
22
32
  ];
23
33
  pluginConfiguration?: ToolbarPluginOptions;
24
- sharedState: {
25
- shouldShowToolbar: boolean;
26
- };
34
+ sharedState: EditorToolbarPluginState;
27
35
  }>;
@@ -1 +1,3 @@
1
1
  export declare const isEventInContainer: (event: Event, containerSelector: string) => boolean;
2
+ export declare const isShortcutToFocusToolbar: (event: KeyboardEvent) => boolean;
3
+ export declare const getFocusableElements: (rootNode: HTMLElement | null) => Array<HTMLElement>;
@@ -1,5 +1,2 @@
1
1
  import type { ToolbarPlugin } from './toolbarPluginType';
2
- export type EditorToolbarPluginState = {
3
- shouldShowToolbar: boolean;
4
- };
5
2
  export declare const toolbarPlugin: ToolbarPlugin;
@@ -5,8 +5,18 @@ import type { EditorViewModePlugin } from '@atlaskit/editor-plugin-editor-viewmo
5
5
  import type { SelectionPlugin } from '@atlaskit/editor-plugin-selection';
6
6
  import type { UserIntentPlugin } from '@atlaskit/editor-plugin-user-intent';
7
7
  import type { UserPreferencesPlugin } from '@atlaskit/editor-plugin-user-preferences';
8
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
8
9
  import type { RegisterComponent } from '@atlaskit/editor-toolbar-model';
9
10
  import type { RegisterComponentsAction, ToolbarPluginOptions } from './types';
11
+ export type EditorToolbarPluginState = {
12
+ selectedNode?: {
13
+ marks: string[];
14
+ node: PMNode;
15
+ nodeType: string;
16
+ pos: number;
17
+ };
18
+ shouldShowToolbar: boolean;
19
+ };
10
20
  export type ToolbarPlugin = NextEditorPlugin<'toolbar', {
11
21
  actions: {
12
22
  getComponents: () => Array<RegisterComponent>;
@@ -21,7 +31,5 @@ export type ToolbarPlugin = NextEditorPlugin<'toolbar', {
21
31
  OptionalPlugin<AnalyticsPlugin>
22
32
  ];
23
33
  pluginConfiguration?: ToolbarPluginOptions;
24
- sharedState: {
25
- shouldShowToolbar: boolean;
26
- };
34
+ sharedState: EditorToolbarPluginState;
27
35
  }>;
@@ -1 +1,3 @@
1
1
  export declare const isEventInContainer: (event: Event, containerSelector: string) => boolean;
2
+ export declare const isShortcutToFocusToolbar: (event: KeyboardEvent) => boolean;
3
+ export declare const getFocusableElements: (rootNode: HTMLElement | null) => Array<HTMLElement>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-toolbar",
3
- "version": "2.1.3",
3
+ "version": "2.1.5",
4
4
  "description": "Toolbar plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -28,6 +28,7 @@
28
28
  "sideEffects": false,
29
29
  "atlaskit:src": "src/index.ts",
30
30
  "dependencies": {
31
+ "@atlaskit/browser-apis": "^0.0.1",
31
32
  "@atlaskit/editor-plugin-analytics": "^5.2.0",
32
33
  "@atlaskit/editor-plugin-connectivity": "^5.0.0",
33
34
  "@atlaskit/editor-plugin-editor-viewmode": "^7.0.0",
@@ -38,13 +39,13 @@
38
39
  "@atlaskit/editor-toolbar": "^0.9.0",
39
40
  "@atlaskit/editor-toolbar-model": "^0.2.0",
40
41
  "@atlaskit/platform-feature-flags-react": "^0.3.0",
41
- "@atlaskit/tmp-editor-statsig": "^12.21.0",
42
+ "@atlaskit/tmp-editor-statsig": "^12.28.0",
42
43
  "@babel/runtime": "^7.0.0",
43
44
  "bind-event-listener": "^3.0.0",
44
45
  "react-intl-next": "npm:react-intl@^5.18.1"
45
46
  },
46
47
  "peerDependencies": {
47
- "@atlaskit/editor-common": "^109.6.0",
48
+ "@atlaskit/editor-common": "^109.12.0",
48
49
  "react": "^18.2.0"
49
50
  },
50
51
  "techstack": {