@atlaskit/editor-plugin-insert-block 28.0.5 → 28.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/ui/registered-insert-menu/RegisteredInsertMenu.js +113 -16
  3. package/dist/cjs/ui/registered-insert-menu/RegisteredInsertMenuContent.js +12 -9
  4. package/dist/cjs/ui/registered-insert-menu/RegisteredInsertMenuItem.js +7 -2
  5. package/dist/cjs/ui/registered-insert-menu/RegisteredInsertMenuList.compiled.css +2 -0
  6. package/dist/cjs/ui/registered-insert-menu/RegisteredInsertMenuList.js +137 -51
  7. package/dist/es2019/ui/registered-insert-menu/RegisteredInsertMenu.js +98 -11
  8. package/dist/es2019/ui/registered-insert-menu/RegisteredInsertMenuContent.js +11 -7
  9. package/dist/es2019/ui/registered-insert-menu/RegisteredInsertMenuItem.js +7 -2
  10. package/dist/es2019/ui/registered-insert-menu/RegisteredInsertMenuList.compiled.css +2 -0
  11. package/dist/es2019/ui/registered-insert-menu/RegisteredInsertMenuList.js +99 -21
  12. package/dist/esm/ui/registered-insert-menu/RegisteredInsertMenu.js +113 -16
  13. package/dist/esm/ui/registered-insert-menu/RegisteredInsertMenuContent.js +13 -10
  14. package/dist/esm/ui/registered-insert-menu/RegisteredInsertMenuItem.js +7 -2
  15. package/dist/esm/ui/registered-insert-menu/RegisteredInsertMenuList.compiled.css +2 -0
  16. package/dist/esm/ui/registered-insert-menu/RegisteredInsertMenuList.js +138 -52
  17. package/dist/types/ui/registered-insert-menu/RegisteredInsertMenu.d.ts +3 -1
  18. package/dist/types/ui/registered-insert-menu/RegisteredInsertMenuItem.d.ts +1 -0
  19. package/dist/types/ui/registered-insert-menu/RegisteredInsertMenuList.d.ts +7 -2
  20. package/package.json +5 -5
@@ -7,6 +7,7 @@ import { getDocument } from '@atlaskit/browser-apis';
7
7
  import { toolbarInsertBlockMessages as messages } from '@atlaskit/editor-common/messages/insert-block';
8
8
  import { MENU } from '@atlaskit/editor-common/quick-insert/keys';
9
9
  import { getQuickInsertMenuModel, getQuickInsertMenuRows, selectQuickInsertCategoryItems } from '@atlaskit/editor-common/quick-insert/registered-menu-model';
10
+ import { useQuickInsertSearchAnalytics } from '@atlaskit/editor-common/quick-insert/use-search-analytics';
10
11
  import { isSectionOverflowItemKey } from '@atlaskit/editor-common/type-ahead-is-section-overflow-item-key';
11
12
  import { TYPE_AHEAD_SURFACE_CONTEXT } from '@atlaskit/editor-common/type-ahead-surface-context';
12
13
  import { createSurfaceContext } from '@atlaskit/editor-ui-control-model/create-surface-context';
@@ -21,6 +22,7 @@ const styles = {
21
22
  export const RegisteredInsertMenu = ({
22
23
  components,
23
24
  emptyStateHandler,
25
+ fireAnalyticsEvent,
24
26
  getItemRank,
25
27
  editorView,
26
28
  isOffline,
@@ -38,6 +40,7 @@ export const RegisteredInsertMenu = ({
38
40
  const [menuOpenId] = useState(() => Symbol('registered-insert-menu-open'));
39
41
  const [query, setQuery] = useState('');
40
42
  const [selectedItemIndex, setSelectedItemIndex] = useState(0);
43
+ const selectedItemIndexRef = useRef(selectedItemIndex);
41
44
  const [renderedItems, setRenderedItems] = useState({
42
45
  startIndex: -1,
43
46
  stopIndex: -1
@@ -65,22 +68,102 @@ export const RegisteredInsertMenu = ({
65
68
  selectCategoryItems: hasSectionOverflowItems ? selectQuickInsertCategoryItems : undefined,
66
69
  surfaceContext
67
70
  }), [components, formatMessage, getItemRank, hasSectionOverflowItems, query, surfaceContext]);
71
+ const [previewActivation, setPreviewActivation] = useState();
72
+ const activePreviewItemKey = (previewActivation === null || previewActivation === void 0 ? void 0 : previewActivation.model) === model ? previewActivation.itemKey : undefined;
73
+ const searchResultCount = useMemo(() => model.sections.reduce((count, section) => count + section.filter(({
74
+ key,
75
+ type
76
+ }) => type === 'menu-item' && !isSectionOverflowItemKey(key)).length, 0), [model.sections]);
77
+ const flushSearchAnalytics = useQuickInsertSearchAnalytics({
78
+ fireAnalyticsEvent,
79
+ query,
80
+ resultCount: searchResultCount,
81
+ surface: 'toolbar'
82
+ });
83
+ const closeWithAnalytics = useCallback(() => {
84
+ flushSearchAnalytics();
85
+ onClose();
86
+ }, [flushSearchAnalytics, onClose]);
87
+ const selectWithAnalytics = useCallback(() => {
88
+ flushSearchAnalytics();
89
+ onSelect();
90
+ }, [flushSearchAnalytics, onSelect]);
68
91
  const selectableItems = useMemo(() => getQuickInsertMenuRows(model).filter(row => row.type === 'menu-item'), [model]);
69
92
  const itemCount = selectableItems.length + (model.footer ? 1 : 0);
70
93
  const selectableItemKeys = useMemo(() => [...selectableItems, ...(model.footer ? [model.footer] : [])].map(({
71
94
  key
72
95
  }) => key).join(','), [model.footer, selectableItems]);
73
96
  useLayoutEffect(() => {
74
- setSelectedItemIndex(itemCount > 0 ? 0 : -1);
97
+ const initialItemIndex = itemCount > 0 ? 0 : -1;
98
+ selectedItemIndexRef.current = initialItemIndex;
99
+ setSelectedItemIndex(initialItemIndex);
75
100
  }, [itemCount, query, selectableItemKeys]);
101
+ useLayoutEffect(() => {
102
+ setPreviewActivation(undefined);
103
+ }, [model, query]);
76
104
  const isActiveItemMounted = selectedItemIndex >= 0 && selectedItemIndex < itemCount && (selectedItemIndex >= renderedItems.startIndex && selectedItemIndex <= renderedItems.stopIndex || Boolean(model.footer) && selectedItemIndex === itemCount - 1);
77
105
  const activeItemId = `${listId}-${selectedItemIndex}`;
106
+ const setKeyboardPreview = useCallback(itemIndex => {
107
+ const item = selectableItems[itemIndex];
108
+ setPreviewActivation(item ? {
109
+ itemKey: item.key,
110
+ model,
111
+ source: 'keyboard'
112
+ } : undefined);
113
+ }, [model, selectableItems]);
78
114
  const selectNextItem = useCallback(() => {
79
- setSelectedItemIndex(index => itemCount === 0 ? -1 : (index + 1) % itemCount);
80
- }, [itemCount]);
115
+ const currentIndex = selectedItemIndexRef.current;
116
+ const nextIndex = itemCount === 0 ? -1 : currentIndex < 0 ? 0 : (currentIndex + 1) % itemCount;
117
+ selectedItemIndexRef.current = nextIndex;
118
+ setSelectedItemIndex(nextIndex);
119
+ setKeyboardPreview(nextIndex);
120
+ }, [itemCount, setKeyboardPreview]);
81
121
  const selectPreviousItem = useCallback(() => {
82
- setSelectedItemIndex(index => itemCount === 0 ? -1 : index <= 0 ? itemCount - 1 : index - 1);
83
- }, [itemCount]);
122
+ const currentIndex = selectedItemIndexRef.current;
123
+ const previousIndex = itemCount === 0 ? -1 : currentIndex <= 0 ? itemCount - 1 : currentIndex - 1;
124
+ selectedItemIndexRef.current = previousIndex;
125
+ setSelectedItemIndex(previousIndex);
126
+ setKeyboardPreview(previousIndex);
127
+ }, [itemCount, setKeyboardPreview]);
128
+ const onItemHover = useCallback((itemIndex, itemKey) => {
129
+ selectedItemIndexRef.current = itemIndex;
130
+ setSelectedItemIndex(itemIndex);
131
+ setPreviewActivation(current => {
132
+ if ((current === null || current === void 0 ? void 0 : current.itemKey) === itemKey && (current === null || current === void 0 ? void 0 : current.model) === model && (current === null || current === void 0 ? void 0 : current.source) === 'pointer') {
133
+ return current;
134
+ }
135
+ return itemKey ? {
136
+ itemKey,
137
+ model,
138
+ source: 'pointer'
139
+ } : undefined;
140
+ });
141
+ }, [model]);
142
+ const onItemFocus = useCallback((itemIndex, itemKey) => {
143
+ selectedItemIndexRef.current = itemIndex;
144
+ setSelectedItemIndex(itemIndex);
145
+ setPreviewActivation(current => {
146
+ if ((current === null || current === void 0 ? void 0 : current.itemKey) === itemKey && (current === null || current === void 0 ? void 0 : current.model) === model) {
147
+ return current;
148
+ }
149
+ return itemKey ? {
150
+ itemKey,
151
+ model,
152
+ source: 'keyboard'
153
+ } : undefined;
154
+ });
155
+ }, [model]);
156
+ const onItemLeave = useCallback(itemKey => {
157
+ setPreviewActivation(current => (current === null || current === void 0 ? void 0 : current.source) === 'pointer' && current.itemKey === itemKey ? undefined : current);
158
+ }, []);
159
+ const onItemBlur = useCallback(itemKey => {
160
+ setPreviewActivation(current => (current === null || current === void 0 ? void 0 : current.itemKey) === itemKey ? undefined : current);
161
+ }, []);
162
+ const onItemVisibilityChange = useCallback((itemKey, isVisible) => {
163
+ if (!isVisible) {
164
+ setPreviewActivation(current => (current === null || current === void 0 ? void 0 : current.source) === 'pointer' && current.itemKey === itemKey ? undefined : current);
165
+ }
166
+ }, []);
84
167
  const selectActiveItem = useCallback(() => {
85
168
  if (isActiveItemMounted) {
86
169
  var _getDocument, _getDocument$getEleme;
@@ -89,12 +172,11 @@ export const RegisteredInsertMenu = ({
89
172
  }, [activeItemId, isActiveItemMounted]);
90
173
  const onQueryChange = useCallback(event => {
91
174
  setQuery(event.currentTarget.value);
92
- setSelectedItemIndex(0);
93
175
  }, []);
94
176
  const onKeyDown = useCallback(event => {
95
177
  if (event.key === 'Escape') {
96
178
  event.preventDefault();
97
- onClose();
179
+ closeWithAnalytics();
98
180
  return;
99
181
  }
100
182
 
@@ -118,7 +200,7 @@ export const RegisteredInsertMenu = ({
118
200
  selectActiveItem();
119
201
  break;
120
202
  }
121
- }, [onClose, selectActiveItem, selectNextItem, selectPreviousItem]);
203
+ }, [closeWithAnalytics, selectActiveItem, selectNextItem, selectPreviousItem]);
122
204
  return /*#__PURE__*/React.createElement(Box, {
123
205
  "data-keyboard-navigation-independent": "",
124
206
  testId: "registered-insert-menu",
@@ -145,11 +227,16 @@ export const RegisteredInsertMenu = ({
145
227
  maxHeight: maxHeight,
146
228
  menuOpenId: menuOpenId,
147
229
  model: model,
148
- onClose: onSelect,
149
- onItemHover: setSelectedItemIndex,
230
+ onClose: selectWithAnalytics,
231
+ onItemFocus: onItemFocus,
232
+ onItemHover: onItemHover,
233
+ onItemBlur: onItemBlur,
234
+ onItemLeave: onItemLeave,
235
+ onItemVisibilityChange: onItemVisibilityChange,
150
236
  onRenderedItemsChange: setRenderedItems,
151
237
  query: query,
152
238
  selectedItemIndex: selectedItemIndex,
153
- surfaceContext: surfaceContext
239
+ surfaceContext: surfaceContext,
240
+ activePreviewItemKey: activePreviewItemKey
154
241
  }));
155
242
  };
@@ -1,8 +1,10 @@
1
- import React, { useCallback, useContext, useState } from 'react';
1
+ import React, { useCallback, useContext, useSyncExternalStore } from 'react';
2
2
  import { MENU } from '@atlaskit/editor-common/quick-insert/keys';
3
3
  import { PlainOutsideClickTargetRefContext } from '@atlaskit/editor-common/ui';
4
4
  import withOuterListeners from '@atlaskit/editor-common/with-outer-listeners';
5
5
  import { RegisteredInsertMenu } from './RegisteredInsertMenu';
6
+ const EMPTY_COMPONENTS = [];
7
+ const subscribeToNothing = () => () => {};
6
8
  const RegisteredInsertMenuContainer = ({
7
9
  children
8
10
  }) => {
@@ -22,12 +24,13 @@ export const RegisteredInsertMenuContent = ({
22
24
  onSelect,
23
25
  target
24
26
  }) => {
25
- var _api$quickInsert, _api$quickInsert$shar, _api$quickInsert2, _api$quickInsert2$sha;
26
- // Keep registrations stable while the open menu is searched or navigated.
27
- const [components] = useState(() => {
28
- var _api$uiControlRegistr, _api$uiControlRegistr2;
29
- return (_api$uiControlRegistr = api === null || api === void 0 ? void 0 : (_api$uiControlRegistr2 = api.uiControlRegistry) === null || _api$uiControlRegistr2 === void 0 ? void 0 : _api$uiControlRegistr2.actions.getComponents(MENU.key)) !== null && _api$uiControlRegistr !== void 0 ? _api$uiControlRegistr : [];
30
- });
27
+ var _api$uiControlRegistr2, _api$uiControlRegistr3, _api$quickInsert, _api$quickInsert$shar, _api$analytics, _api$quickInsert2, _api$quickInsert2$sha;
28
+ const getComponents = useCallback(() => {
29
+ var _api$uiControlRegistr;
30
+ const components = api === null || api === void 0 ? void 0 : (_api$uiControlRegistr = api.uiControlRegistry) === null || _api$uiControlRegistr === void 0 ? void 0 : _api$uiControlRegistr.actions.getComponents(MENU.key);
31
+ return components !== null && components !== void 0 && components.length ? components : EMPTY_COMPONENTS;
32
+ }, [api]);
33
+ const components = useSyncExternalStore((_api$uiControlRegistr2 = api === null || api === void 0 ? void 0 : (_api$uiControlRegistr3 = api.uiControlRegistry) === null || _api$uiControlRegistr3 === void 0 ? void 0 : _api$uiControlRegistr3.actions.subscribe) !== null && _api$uiControlRegistr2 !== void 0 ? _api$uiControlRegistr2 : subscribeToNothing, getComponents, getComponents);
31
34
  const onClickOutside = useCallback(event => {
32
35
  if (event.target instanceof Node && target !== null && target !== void 0 && target.contains(event.target)) {
33
36
  return;
@@ -40,6 +43,7 @@ export const RegisteredInsertMenuContent = ({
40
43
  components: components,
41
44
  editorView: editorView,
42
45
  emptyStateHandler: api === null || api === void 0 ? void 0 : (_api$quickInsert = api.quickInsert) === null || _api$quickInsert === void 0 ? void 0 : (_api$quickInsert$shar = _api$quickInsert.sharedState.currentState()) === null || _api$quickInsert$shar === void 0 ? void 0 : _api$quickInsert$shar.emptyStateHandler,
46
+ fireAnalyticsEvent: api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.fireAnalyticsEvent,
43
47
  getItemRank: api === null || api === void 0 ? void 0 : (_api$quickInsert2 = api.quickInsert) === null || _api$quickInsert2 === void 0 ? void 0 : (_api$quickInsert2$sha = _api$quickInsert2.sharedState.currentState()) === null || _api$quickInsert2$sha === void 0 ? void 0 : _api$quickInsert2$sha.getItemRank,
44
48
  isOffline: isOffline,
45
49
  maxHeight: maxHeight,
@@ -1,7 +1,9 @@
1
- import React, { useCallback, useMemo } from 'react';
1
+ import React, { createContext, useCallback, useContext, useMemo } from 'react';
2
2
  import { INPUT_METHOD } from '@atlaskit/editor-common/analytics/types/enums';
3
3
  import { insertSelectedItem } from '@atlaskit/editor-common/insert';
4
4
  import { QuickInsertProvider } from '@atlaskit/editor-common/quick-insert/provider';
5
+ // Preview updates must remain live while react-virtualized caches row elements during scrolling.
6
+ export const RegisteredInsertMenuPreviewContext = /*#__PURE__*/createContext(undefined);
5
7
  export const RegisteredInsertMenuItem = ({
6
8
  editorView,
7
9
  id,
@@ -14,6 +16,8 @@ export const RegisteredInsertMenuItem = ({
14
16
  }) => {
15
17
  var _registration$compone;
16
18
  const Component = (_registration$compone = registration.component) !== null && _registration$compone !== void 0 ? _registration$compone : React.Fragment;
19
+ const activePreviewItemKey = useContext(RegisteredInsertMenuPreviewContext);
20
+ const isPreviewActive = activePreviewItemKey === registration.key;
17
21
  const select = useCallback(selectionHandler => {
18
22
  // Close first so a registered item may safely focus the editor or open its picker.
19
23
  onClose();
@@ -36,13 +40,14 @@ export const RegisteredInsertMenuItem = ({
36
40
  isOffline,
37
41
  item: {
38
42
  id,
43
+ isPreviewActive,
39
44
  isSelected
40
45
  },
41
46
  menuOpenId,
42
47
  select,
43
48
  surface: 'toolbar',
44
49
  surfaceContext
45
- }), [editorView, id, isOffline, isSelected, menuOpenId, select, surfaceContext]);
50
+ }), [editorView, id, isOffline, isPreviewActive, isSelected, menuOpenId, select, surfaceContext]);
46
51
  return /*#__PURE__*/React.createElement(QuickInsertProvider, {
47
52
  value: contextValue
48
53
  }, /*#__PURE__*/React.createElement(Component, null, null));
@@ -4,6 +4,8 @@
4
4
  ._1e0c1txw{display:flex}
5
5
  ._1i531l7x{border-top-color:var(--ds-border,#0b120e24)}
6
6
  ._1o9zidpf{flex-shrink:0}
7
+ ._1q51u2gc{padding-block-start:var(--ds-space-100,8px)}
7
8
  ._1tkeidpf{min-height:0}
8
9
  ._2lx21bp4{flex-direction:column}
10
+ ._85i5u2gc{padding-block-end:var(--ds-space-100,8px)}
9
11
  ._uwhke4h9{border-top-width:var(--ds-border-width,1px)}
@@ -5,10 +5,11 @@ import { ax, ix } from "@compiled/react/runtime";
5
5
  import React, { useCallback, useLayoutEffect, useMemo, useRef } from 'react';
6
6
  import { CellMeasurer, CellMeasurerCache } from 'react-virtualized/dist/commonjs/CellMeasurer';
7
7
  import { List } from 'react-virtualized/dist/commonjs/List';
8
+ import { QuickInsertEmptyState } from '@atlaskit/editor-common/quick-insert/empty-state';
8
9
  import { getQuickInsertMenuRows } from '@atlaskit/editor-common/quick-insert/registered-menu-model';
9
10
  import { useMenuListHeight } from '@atlaskit/editor-common/quick-insert/use-menu-list-height';
10
11
  import { Box } from '@atlaskit/primitives/compiled';
11
- import { RegisteredInsertMenuItem } from './RegisteredInsertMenuItem';
12
+ import { RegisteredInsertMenuItem, RegisteredInsertMenuPreviewContext } from './RegisteredInsertMenuItem';
12
13
  const ESTIMATED_ROW_HEIGHT = 44;
13
14
  // 44px item + 1px separator.
14
15
  const MENU_FOOTER_HEIGHT = 45;
@@ -21,7 +22,46 @@ const listAriaProps = {
21
22
  const styles = {
22
23
  content: "_1e0c1txw _2lx21bp4 _1tkeidpf",
23
24
  scrollable: "_16jlkb7n _1tkeidpf _18m91wug",
24
- footer: "_1i531l7x _15a5nqa1 _uwhke4h9 _1o9zidpf"
25
+ footer: "_1o9zidpf",
26
+ footerWithDivider: "_1i531l7x _15a5nqa1 _uwhke4h9 _1o9zidpf",
27
+ externalControls: "_1i531l7x _15a5nqa1 _uwhke4h9 _1q51u2gc _85i5u2gc"
28
+ };
29
+ const RegisteredInsertMenuItemRow = ({
30
+ editorView,
31
+ id,
32
+ isOffline,
33
+ isSelected,
34
+ itemIndex,
35
+ menuOpenId,
36
+ onClose,
37
+ onItemFocus,
38
+ onItemBlur,
39
+ onItemHover,
40
+ onItemLeave,
41
+ registration,
42
+ surfaceContext
43
+ }) => {
44
+ useLayoutEffect(() => () => onItemLeave(registration.key), [onItemLeave, registration.key]);
45
+ const onBlur = useCallback(event => {
46
+ if (!event.currentTarget.contains(event.relatedTarget)) {
47
+ onItemBlur(registration.key);
48
+ }
49
+ }, [onItemBlur, registration.key]);
50
+ return /*#__PURE__*/React.createElement("div", {
51
+ onBlur: onBlur,
52
+ onFocus: () => onItemFocus(itemIndex, registration.key),
53
+ onMouseLeave: () => onItemLeave(registration.key),
54
+ onMouseMove: () => onItemHover(itemIndex, registration.key)
55
+ }, /*#__PURE__*/React.createElement(RegisteredInsertMenuItem, {
56
+ editorView: editorView,
57
+ id: id,
58
+ isOffline: isOffline,
59
+ isSelected: isSelected,
60
+ menuOpenId: menuOpenId,
61
+ onClose: onClose,
62
+ registration: registration,
63
+ surfaceContext: surfaceContext
64
+ }));
25
65
  };
26
66
  export const RegisteredInsertMenuList = ({
27
67
  editorView,
@@ -33,11 +73,16 @@ export const RegisteredInsertMenuList = ({
33
73
  menuOpenId,
34
74
  model,
35
75
  onClose,
76
+ onItemFocus,
77
+ onItemBlur,
36
78
  query,
37
79
  onItemHover,
80
+ onItemLeave,
81
+ onItemVisibilityChange,
38
82
  onRenderedItemsChange,
39
83
  selectedItemIndex,
40
- surfaceContext
84
+ surfaceContext,
85
+ activePreviewItemKey
41
86
  }) => {
42
87
  var _model$root$component, _model$root;
43
88
  const Root = (_model$root$component = (_model$root = model.root) === null || _model$root === void 0 ? void 0 : _model$root.component) !== null && _model$root$component !== void 0 ? _model$root$component : React.Fragment;
@@ -84,16 +129,31 @@ export const RegisteredInsertMenuList = ({
84
129
  }, [cache, rowKeySignature]);
85
130
  const onRowsRendered = useCallback(({
86
131
  overscanStartIndex,
87
- overscanStopIndex
132
+ overscanStopIndex,
133
+ startIndex,
134
+ stopIndex
88
135
  }) => {
89
136
  var _indexes$, _indexes;
137
+ if (activePreviewItemKey) {
138
+ const previewRowIndex = rows.findIndex(({
139
+ key
140
+ }) => key === activePreviewItemKey);
141
+ if (previewRowIndex < startIndex || previewRowIndex > stopIndex) {
142
+ onItemVisibilityChange(activePreviewItemKey, false);
143
+ }
144
+ }
90
145
  updateHeight();
91
146
  const indexes = [...itemIndexByRowIndex].filter(([rowIndex]) => rowIndex >= overscanStartIndex && rowIndex <= overscanStopIndex).map(([, itemIndex]) => itemIndex);
92
147
  onRenderedItemsChange({
93
148
  startIndex: (_indexes$ = indexes[0]) !== null && _indexes$ !== void 0 ? _indexes$ : -1,
94
149
  stopIndex: (_indexes = indexes[indexes.length - 1]) !== null && _indexes !== void 0 ? _indexes : -1
95
150
  });
96
- }, [itemIndexByRowIndex, onRenderedItemsChange, updateHeight]);
151
+ }, [activePreviewItemKey, itemIndexByRowIndex, onItemVisibilityChange, onRenderedItemsChange, rows, updateHeight]);
152
+ const onScrollableContentScroll = useCallback(event => {
153
+ if (event.target === event.currentTarget && activePreviewItemKey) {
154
+ onItemVisibilityChange(activePreviewItemKey, false);
155
+ }
156
+ }, [activePreviewItemKey, onItemVisibilityChange]);
97
157
  const renderRow = useCallback(({
98
158
  index,
99
159
  key,
@@ -106,19 +166,21 @@ export const RegisteredInsertMenuList = ({
106
166
  return null;
107
167
  }
108
168
  const itemIndex = itemIndexByRowIndex.get(index);
109
- const rowContent = registration.type === 'menu-section' ? ( /*#__PURE__*/React.createElement((_registration$compone = registration.component) !== null && _registration$compone !== void 0 ? _registration$compone : React.Fragment, null, null)) : /*#__PURE__*/React.createElement("div", {
110
- onFocus: () => onItemHover(itemIndex !== null && itemIndex !== void 0 ? itemIndex : 0),
111
- onMouseMove: () => onItemHover(itemIndex !== null && itemIndex !== void 0 ? itemIndex : 0)
112
- }, /*#__PURE__*/React.createElement(RegisteredInsertMenuItem, {
169
+ const rowContent = registration.type === 'menu-section' ? ( /*#__PURE__*/React.createElement((_registration$compone = registration.component) !== null && _registration$compone !== void 0 ? _registration$compone : React.Fragment, null, null)) : /*#__PURE__*/React.createElement(RegisteredInsertMenuItemRow, {
113
170
  editorView: editorView,
114
171
  id: `${listId}-${itemIndex}`,
115
172
  isOffline: isOffline,
116
173
  isSelected: itemIndex === selectedItemIndex,
174
+ itemIndex: itemIndex !== null && itemIndex !== void 0 ? itemIndex : 0,
117
175
  menuOpenId: menuOpenId,
118
176
  onClose: onClose,
177
+ onItemFocus: onItemFocus,
178
+ onItemBlur: onItemBlur,
179
+ onItemHover: onItemHover,
180
+ onItemLeave: onItemLeave,
119
181
  registration: registration,
120
182
  surfaceContext: surfaceContext
121
- }));
183
+ });
122
184
  return /*#__PURE__*/React.createElement(CellMeasurer, {
123
185
  key: key,
124
186
  cache: cache,
@@ -132,19 +194,30 @@ export const RegisteredInsertMenuList = ({
132
194
  // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
133
195
  ,
134
196
  style: style
135
- }, rowContent));
136
- }, [cache, editorView, isOffline, itemIndexByRowIndex, listId, menuOpenId, onClose, onItemHover, rows, selectedItemIndex, surfaceContext]);
197
+ }, /*#__PURE__*/React.createElement(Box, {
198
+ paddingBlockEnd: index === rows.length - 1 ? 'space.100' : undefined
199
+ }, rowContent)));
200
+ }, [cache, editorView, isOffline, itemIndexByRowIndex, listId, menuOpenId, onClose, onItemFocus, onItemBlur, onItemHover, onItemLeave, rows, selectedItemIndex, surfaceContext]);
137
201
  const footerItemIndex = itemIndexByRowIndex.size;
138
202
  const footerItemId = `${listId}-${footerItemIndex}`;
139
203
  const hasExternalControls = model.fallbackItems !== undefined && emptyStateHandler !== undefined;
204
+ const hasNoSearchResults = query !== '' && rows.length === 0;
205
+ const externalControls = hasExternalControls && emptyStateHandler ? emptyStateHandler({
206
+ mode: 'inline',
207
+ searchTerm: query,
208
+ showNoResultsMessage: false
209
+ }) : null;
210
+ const showFooterDivider = !hasNoSearchResults || Boolean(externalControls);
140
211
  const listMaxHeight = Math.max(0, maxHeight - MENU_SEARCH_HEIGHT - (model.footer ? MENU_FOOTER_HEIGHT : 0));
141
212
  const contentStyle = {
142
213
  maxHeight: Math.max(0, maxHeight - MENU_SEARCH_HEIGHT)
143
214
  };
144
215
  const onFooterItemHover = useCallback(() => onItemHover(footerItemIndex), [footerItemIndex, onItemHover]);
216
+ const onFooterItemFocus = useCallback(() => onItemFocus(footerItemIndex), [footerItemIndex, onItemFocus]);
145
217
  const footer = model.footer ? /*#__PURE__*/React.createElement(Box, {
146
- xcss: styles.footer,
147
- onFocus: onFooterItemHover,
218
+ testId: "registered-insert-menu-footer",
219
+ xcss: showFooterDivider ? styles.footerWithDivider : styles.footer,
220
+ onFocus: onFooterItemFocus,
148
221
  onMouseMove: onFooterItemHover
149
222
  }, /*#__PURE__*/React.createElement(RegisteredInsertMenuItem, {
150
223
  editorView: editorView,
@@ -159,11 +232,14 @@ export const RegisteredInsertMenuList = ({
159
232
  if (!model.root) {
160
233
  return null;
161
234
  }
162
- return /*#__PURE__*/React.createElement(Root, null, /*#__PURE__*/React.createElement(Box, {
235
+ return /*#__PURE__*/React.createElement(Root, null, /*#__PURE__*/React.createElement(RegisteredInsertMenuPreviewContext.Provider, {
236
+ value: activePreviewItemKey
237
+ }, /*#__PURE__*/React.createElement(Box, {
163
238
  xcss: styles.content,
164
239
  style: contentStyle
165
240
  }, /*#__PURE__*/React.createElement(Box, {
166
- xcss: styles.scrollable
241
+ xcss: styles.scrollable,
242
+ onScroll: onScrollableContentScroll
167
243
  }, /*#__PURE__*/React.createElement(Box, {
168
244
  "aria-label": listLabel,
169
245
  "aria-owns": model.footer ? footerItemId : undefined,
@@ -185,9 +261,11 @@ export const RegisteredInsertMenuList = ({
185
261
  scrollToAlignment: "auto",
186
262
  scrollToIndex: selectedRowIndex,
187
263
  width: LIST_WIDTH
188
- }))), hasExternalControls && /*#__PURE__*/React.createElement(Box, null, emptyStateHandler({
189
- mode: 'inline',
190
- searchTerm: query,
191
- showNoResultsMessage: false
192
- }))), footer));
264
+ }))), hasNoSearchResults && /*#__PURE__*/React.createElement(QuickInsertEmptyState, {
265
+ mode: "inline",
266
+ spacing: "compact"
267
+ }), externalControls && /*#__PURE__*/React.createElement(Box, {
268
+ testId: "registered-insert-menu-external-controls",
269
+ xcss: styles.externalControls
270
+ }, externalControls)), footer)));
193
271
  };
@@ -9,6 +9,7 @@ import { getDocument } from '@atlaskit/browser-apis';
9
9
  import { toolbarInsertBlockMessages as messages } from '@atlaskit/editor-common/messages/insert-block';
10
10
  import { MENU } from '@atlaskit/editor-common/quick-insert/keys';
11
11
  import { getQuickInsertMenuModel, getQuickInsertMenuRows, selectQuickInsertCategoryItems } from '@atlaskit/editor-common/quick-insert/registered-menu-model';
12
+ import { useQuickInsertSearchAnalytics } from '@atlaskit/editor-common/quick-insert/use-search-analytics';
12
13
  import { isSectionOverflowItemKey } from '@atlaskit/editor-common/type-ahead-is-section-overflow-item-key';
13
14
  import { TYPE_AHEAD_SURFACE_CONTEXT } from '@atlaskit/editor-common/type-ahead-surface-context';
14
15
  import { createSurfaceContext } from '@atlaskit/editor-ui-control-model/create-surface-context';
@@ -23,6 +24,7 @@ var styles = {
23
24
  export var RegisteredInsertMenu = function RegisteredInsertMenu(_ref) {
24
25
  var components = _ref.components,
25
26
  emptyStateHandler = _ref.emptyStateHandler,
27
+ fireAnalyticsEvent = _ref.fireAnalyticsEvent,
26
28
  getItemRank = _ref.getItemRank,
27
29
  editorView = _ref.editorView,
28
30
  isOffline = _ref.isOffline,
@@ -49,6 +51,7 @@ export var RegisteredInsertMenu = function RegisteredInsertMenu(_ref) {
49
51
  _useState6 = _slicedToArray(_useState5, 2),
50
52
  selectedItemIndex = _useState6[0],
51
53
  setSelectedItemIndex = _useState6[1];
54
+ var selectedItemIndexRef = useRef(selectedItemIndex);
52
55
  var _useState7 = useState({
53
56
  startIndex: -1,
54
57
  stopIndex: -1
@@ -86,6 +89,34 @@ export var RegisteredInsertMenu = function RegisteredInsertMenu(_ref) {
86
89
  surfaceContext: surfaceContext
87
90
  });
88
91
  }, [components, formatMessage, getItemRank, hasSectionOverflowItems, query, surfaceContext]);
92
+ var _useState9 = useState(),
93
+ _useState0 = _slicedToArray(_useState9, 2),
94
+ previewActivation = _useState0[0],
95
+ setPreviewActivation = _useState0[1];
96
+ var activePreviewItemKey = (previewActivation === null || previewActivation === void 0 ? void 0 : previewActivation.model) === model ? previewActivation.itemKey : undefined;
97
+ var searchResultCount = useMemo(function () {
98
+ return model.sections.reduce(function (count, section) {
99
+ return count + section.filter(function (_ref3) {
100
+ var key = _ref3.key,
101
+ type = _ref3.type;
102
+ return type === 'menu-item' && !isSectionOverflowItemKey(key);
103
+ }).length;
104
+ }, 0);
105
+ }, [model.sections]);
106
+ var flushSearchAnalytics = useQuickInsertSearchAnalytics({
107
+ fireAnalyticsEvent: fireAnalyticsEvent,
108
+ query: query,
109
+ resultCount: searchResultCount,
110
+ surface: 'toolbar'
111
+ });
112
+ var closeWithAnalytics = useCallback(function () {
113
+ flushSearchAnalytics();
114
+ onClose();
115
+ }, [flushSearchAnalytics, onClose]);
116
+ var selectWithAnalytics = useCallback(function () {
117
+ flushSearchAnalytics();
118
+ onSelect();
119
+ }, [flushSearchAnalytics, onSelect]);
89
120
  var selectableItems = useMemo(function () {
90
121
  return getQuickInsertMenuRows(model).filter(function (row) {
91
122
  return row.type === 'menu-item';
@@ -93,26 +124,88 @@ export var RegisteredInsertMenu = function RegisteredInsertMenu(_ref) {
93
124
  }, [model]);
94
125
  var itemCount = selectableItems.length + (model.footer ? 1 : 0);
95
126
  var selectableItemKeys = useMemo(function () {
96
- return [].concat(_toConsumableArray(selectableItems), _toConsumableArray(model.footer ? [model.footer] : [])).map(function (_ref3) {
97
- var key = _ref3.key;
127
+ return [].concat(_toConsumableArray(selectableItems), _toConsumableArray(model.footer ? [model.footer] : [])).map(function (_ref4) {
128
+ var key = _ref4.key;
98
129
  return key;
99
130
  }).join(',');
100
131
  }, [model.footer, selectableItems]);
101
132
  useLayoutEffect(function () {
102
- setSelectedItemIndex(itemCount > 0 ? 0 : -1);
133
+ var initialItemIndex = itemCount > 0 ? 0 : -1;
134
+ selectedItemIndexRef.current = initialItemIndex;
135
+ setSelectedItemIndex(initialItemIndex);
103
136
  }, [itemCount, query, selectableItemKeys]);
137
+ useLayoutEffect(function () {
138
+ setPreviewActivation(undefined);
139
+ }, [model, query]);
104
140
  var isActiveItemMounted = selectedItemIndex >= 0 && selectedItemIndex < itemCount && (selectedItemIndex >= renderedItems.startIndex && selectedItemIndex <= renderedItems.stopIndex || Boolean(model.footer) && selectedItemIndex === itemCount - 1);
105
141
  var activeItemId = "".concat(listId, "-").concat(selectedItemIndex);
142
+ var setKeyboardPreview = useCallback(function (itemIndex) {
143
+ var item = selectableItems[itemIndex];
144
+ setPreviewActivation(item ? {
145
+ itemKey: item.key,
146
+ model: model,
147
+ source: 'keyboard'
148
+ } : undefined);
149
+ }, [model, selectableItems]);
106
150
  var selectNextItem = useCallback(function () {
107
- setSelectedItemIndex(function (index) {
108
- return itemCount === 0 ? -1 : (index + 1) % itemCount;
109
- });
110
- }, [itemCount]);
151
+ var currentIndex = selectedItemIndexRef.current;
152
+ var nextIndex = itemCount === 0 ? -1 : currentIndex < 0 ? 0 : (currentIndex + 1) % itemCount;
153
+ selectedItemIndexRef.current = nextIndex;
154
+ setSelectedItemIndex(nextIndex);
155
+ setKeyboardPreview(nextIndex);
156
+ }, [itemCount, setKeyboardPreview]);
111
157
  var selectPreviousItem = useCallback(function () {
112
- setSelectedItemIndex(function (index) {
113
- return itemCount === 0 ? -1 : index <= 0 ? itemCount - 1 : index - 1;
158
+ var currentIndex = selectedItemIndexRef.current;
159
+ var previousIndex = itemCount === 0 ? -1 : currentIndex <= 0 ? itemCount - 1 : currentIndex - 1;
160
+ selectedItemIndexRef.current = previousIndex;
161
+ setSelectedItemIndex(previousIndex);
162
+ setKeyboardPreview(previousIndex);
163
+ }, [itemCount, setKeyboardPreview]);
164
+ var onItemHover = useCallback(function (itemIndex, itemKey) {
165
+ selectedItemIndexRef.current = itemIndex;
166
+ setSelectedItemIndex(itemIndex);
167
+ setPreviewActivation(function (current) {
168
+ if ((current === null || current === void 0 ? void 0 : current.itemKey) === itemKey && (current === null || current === void 0 ? void 0 : current.model) === model && (current === null || current === void 0 ? void 0 : current.source) === 'pointer') {
169
+ return current;
170
+ }
171
+ return itemKey ? {
172
+ itemKey: itemKey,
173
+ model: model,
174
+ source: 'pointer'
175
+ } : undefined;
176
+ });
177
+ }, [model]);
178
+ var onItemFocus = useCallback(function (itemIndex, itemKey) {
179
+ selectedItemIndexRef.current = itemIndex;
180
+ setSelectedItemIndex(itemIndex);
181
+ setPreviewActivation(function (current) {
182
+ if ((current === null || current === void 0 ? void 0 : current.itemKey) === itemKey && (current === null || current === void 0 ? void 0 : current.model) === model) {
183
+ return current;
184
+ }
185
+ return itemKey ? {
186
+ itemKey: itemKey,
187
+ model: model,
188
+ source: 'keyboard'
189
+ } : undefined;
190
+ });
191
+ }, [model]);
192
+ var onItemLeave = useCallback(function (itemKey) {
193
+ setPreviewActivation(function (current) {
194
+ return (current === null || current === void 0 ? void 0 : current.source) === 'pointer' && current.itemKey === itemKey ? undefined : current;
195
+ });
196
+ }, []);
197
+ var onItemBlur = useCallback(function (itemKey) {
198
+ setPreviewActivation(function (current) {
199
+ return (current === null || current === void 0 ? void 0 : current.itemKey) === itemKey ? undefined : current;
114
200
  });
115
- }, [itemCount]);
201
+ }, []);
202
+ var onItemVisibilityChange = useCallback(function (itemKey, isVisible) {
203
+ if (!isVisible) {
204
+ setPreviewActivation(function (current) {
205
+ return (current === null || current === void 0 ? void 0 : current.source) === 'pointer' && current.itemKey === itemKey ? undefined : current;
206
+ });
207
+ }
208
+ }, []);
116
209
  var selectActiveItem = useCallback(function () {
117
210
  if (isActiveItemMounted) {
118
211
  var _getDocument;
@@ -121,12 +214,11 @@ export var RegisteredInsertMenu = function RegisteredInsertMenu(_ref) {
121
214
  }, [activeItemId, isActiveItemMounted]);
122
215
  var onQueryChange = useCallback(function (event) {
123
216
  setQuery(event.currentTarget.value);
124
- setSelectedItemIndex(0);
125
217
  }, []);
126
218
  var onKeyDown = useCallback(function (event) {
127
219
  if (event.key === 'Escape') {
128
220
  event.preventDefault();
129
- onClose();
221
+ closeWithAnalytics();
130
222
  return;
131
223
  }
132
224
 
@@ -150,7 +242,7 @@ export var RegisteredInsertMenu = function RegisteredInsertMenu(_ref) {
150
242
  selectActiveItem();
151
243
  break;
152
244
  }
153
- }, [onClose, selectActiveItem, selectNextItem, selectPreviousItem]);
245
+ }, [closeWithAnalytics, selectActiveItem, selectNextItem, selectPreviousItem]);
154
246
  return /*#__PURE__*/React.createElement(Box, {
155
247
  "data-keyboard-navigation-independent": "",
156
248
  testId: "registered-insert-menu",
@@ -177,11 +269,16 @@ export var RegisteredInsertMenu = function RegisteredInsertMenu(_ref) {
177
269
  maxHeight: maxHeight,
178
270
  menuOpenId: menuOpenId,
179
271
  model: model,
180
- onClose: onSelect,
181
- onItemHover: setSelectedItemIndex,
272
+ onClose: selectWithAnalytics,
273
+ onItemFocus: onItemFocus,
274
+ onItemHover: onItemHover,
275
+ onItemBlur: onItemBlur,
276
+ onItemLeave: onItemLeave,
277
+ onItemVisibilityChange: onItemVisibilityChange,
182
278
  onRenderedItemsChange: setRenderedItems,
183
279
  query: query,
184
280
  selectedItemIndex: selectedItemIndex,
185
- surfaceContext: surfaceContext
281
+ surfaceContext: surfaceContext,
282
+ activePreviewItemKey: activePreviewItemKey
186
283
  }));
187
284
  };