@atlaskit/editor-plugin-type-ahead 2.1.1 → 2.1.2

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,17 @@
1
1
  # @atlaskit/editor-plugin-type-ahead
2
2
 
3
+ ## 2.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#122304](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/122304)
8
+ [`55aeeb7141654`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/55aeeb7141654) -
9
+ [HOT-115591] Fix emoji popup close when scrolling
10
+ - [#122337](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/122337)
11
+ [`2775fb4a3b7d6`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2775fb4a3b7d6) -
12
+ [ux] [ED-26824] Add keyboard support (up/down to navigate, enter to insert) to view more button
13
+ - Updated dependencies
14
+
3
15
  ## 2.1.1
4
16
 
5
17
  ### Patch Changes
@@ -21,6 +21,7 @@ var _constants = require("../pm-plugins/constants");
21
21
  var _utils = require("../pm-plugins/utils");
22
22
  var _AssistiveText = require("./AssistiveText");
23
23
  var _TypeAheadListItem = require("./TypeAheadListItem");
24
+ var _ViewMore = require("./ViewMore");
24
25
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
25
26
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
26
27
  /**
@@ -63,7 +64,8 @@ var TypeAheadListComponent = /*#__PURE__*/_react.default.memo(function (_ref2) {
63
64
  triggerHandler = _ref2.triggerHandler,
64
65
  moreElementsInQuickInsertViewEnabled = _ref2.moreElementsInQuickInsertViewEnabled,
65
66
  api = _ref2.api,
66
- showViewMore = _ref2.showViewMore;
67
+ showViewMore = _ref2.showViewMore,
68
+ onViewMoreClick = _ref2.onViewMoreClick;
67
69
  var listRef = (0, _react.useRef)();
68
70
  var listContainerRef = (0, _react.useRef)(null);
69
71
  var lastVisibleIndexes = (0, _react.useRef)({
@@ -72,7 +74,10 @@ var TypeAheadListComponent = /*#__PURE__*/_react.default.memo(function (_ref2) {
72
74
  startIndex: 0,
73
75
  stopIndex: 0
74
76
  });
75
- var estimatedHeight = items.length * LIST_ITEM_ESTIMATED_HEIGHT;
77
+
78
+ // Exclude view more item from the count
79
+ var itemsLength = showViewMore ? items.length - 1 : items.length;
80
+ var estimatedHeight = itemsLength * LIST_ITEM_ESTIMATED_HEIGHT;
76
81
  var _useState = (0, _react.useState)(Math.min(estimatedHeight, fitHeight)),
77
82
  _useState2 = (0, _slicedToArray2.default)(_useState, 2),
78
83
  height = _useState2[0],
@@ -138,7 +143,10 @@ var TypeAheadListComponent = /*#__PURE__*/_react.default.memo(function (_ref2) {
138
143
  // to calculate each height. THen, we can schedule a new frame when this one finishs.
139
144
  requestAnimationFrame(function () {
140
145
  requestAnimationFrame(function () {
141
- var isSelectedItemVisible = selectedIndex >= lastVisibleStartIndex && selectedIndex <= lastVisibleStopIndex;
146
+ var isViewMoreSelected = showViewMore && selectedIndex === itemsLength;
147
+ var isSelectedItemVisible = selectedIndex >= lastVisibleStartIndex && selectedIndex <= lastVisibleStopIndex ||
148
+ // view more is always visible, hence no scrolling
149
+ isViewMoreSelected;
142
150
 
143
151
  //Should scroll to the list item only when the selectedIndex >= 0 and item is not visible
144
152
  if (!isSelectedItemVisible && selectedIndex !== -1) {
@@ -148,7 +156,7 @@ var TypeAheadListComponent = /*#__PURE__*/_react.default.memo(function (_ref2) {
148
156
  }
149
157
  });
150
158
  });
151
- }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex]);
159
+ }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex, itemsLength, showViewMore]);
152
160
  var _onMouseMove = function onMouseMove(event, index) {
153
161
  event.preventDefault();
154
162
  event.stopPropagation();
@@ -161,7 +169,10 @@ var TypeAheadListComponent = /*#__PURE__*/_react.default.memo(function (_ref2) {
161
169
  if (!listRef.current) {
162
170
  return;
163
171
  }
164
- var isSelectedItemVisible = selectedIndex >= lastVisibleStartIndex && selectedIndex <= lastVisibleStopIndex;
172
+ var isViewMoreSelected = showViewMore && selectedIndex === itemsLength;
173
+ var isSelectedItemVisible = selectedIndex >= lastVisibleStartIndex && selectedIndex <= lastVisibleStopIndex ||
174
+ // view more is always visible, hence no scrolling
175
+ isViewMoreSelected;
165
176
 
166
177
  //Should scroll to the list item only when the selectedIndex >= 0 and item is not visible
167
178
  if (!isSelectedItemVisible && selectedIndex !== -1) {
@@ -169,7 +180,7 @@ var TypeAheadListComponent = /*#__PURE__*/_react.default.memo(function (_ref2) {
169
180
  } else if (selectedIndex === -1) {
170
181
  listRef.current.scrollToRow(0);
171
182
  }
172
- }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex]);
183
+ }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex, itemsLength, showViewMore]);
173
184
  (0, _react.useLayoutEffect)(function () {
174
185
  setCache(new _CellMeasurer.CellMeasurerCache({
175
186
  fixedWidth: true,
@@ -187,13 +198,15 @@ var TypeAheadListComponent = /*#__PURE__*/_react.default.memo(function (_ref2) {
187
198
  });
188
199
  }, [items]);
189
200
  (0, _react.useLayoutEffect)(function () {
190
- var height = Math.min(items.reduce(function (prevValue, currentValue, index) {
201
+ // Exclude view more item from the count
202
+ var itemsToRender = showViewMore ? items.slice(0, -1) : items;
203
+ var height = Math.min(itemsToRender.reduce(function (prevValue, currentValue, index) {
191
204
  return prevValue + cache.rowHeight({
192
205
  index: index
193
206
  });
194
207
  }, 0), fitHeight);
195
208
  setHeight(height);
196
- }, [items, cache, fitHeight]);
209
+ }, [items, cache, fitHeight, showViewMore]);
197
210
  (0, _react.useLayoutEffect)(function () {
198
211
  if (!listContainerRef.current) {
199
212
  return;
@@ -246,7 +259,7 @@ var TypeAheadListComponent = /*#__PURE__*/_react.default.memo(function (_ref2) {
246
259
  // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
247
260
  element === null || element === void 0 || element.removeEventListener('keydown', handleKeyDown);
248
261
  };
249
- }, [editorView.state, focusTargetElement, selectNextItem, selectPreviousItem, selectedIndex, onItemClick, items.length]);
262
+ }, [editorView.state, focusTargetElement, selectNextItem, selectPreviousItem, selectedIndex, onItemClick, itemsLength]);
250
263
  var firstOnlineSupportedRow = (0, _react.useMemo)(function () {
251
264
  return items.findIndex(function (item) {
252
265
  return item.isDisabledOffline !== true;
@@ -276,7 +289,7 @@ var TypeAheadListComponent = /*#__PURE__*/_react.default.memo(function (_ref2) {
276
289
  key: items[index].title,
277
290
  item: currentItem,
278
291
  firstOnlineSupportedIndex: firstOnlineSupportedRow,
279
- itemsLength: items.length,
292
+ itemsLength: itemsLength,
280
293
  itemIndex: index,
281
294
  selectedIndex: selectedIndex,
282
295
  onItemClick: actions.onItemClick,
@@ -306,8 +319,10 @@ var TypeAheadListComponent = /*#__PURE__*/_react.default.memo(function (_ref2) {
306
319
  })));
307
320
  var ListContent = (0, _react2.jsx)(_List.List, {
308
321
  rowRenderer: renderRow,
309
- ref: listRef,
310
- rowCount: items.length,
322
+ ref: listRef
323
+ // Skip rendering the view more button in the list
324
+ ,
325
+ rowCount: itemsLength,
311
326
  rowHeight: cache.rowHeight,
312
327
  onRowsRendered: onItemsRendered,
313
328
  width: LIST_WIDTH,
@@ -341,8 +356,11 @@ var TypeAheadListComponent = /*#__PURE__*/_react.default.memo(function (_ref2) {
341
356
  }, (0, _react2.jsx)("div", {
342
357
  id: menuGroupId,
343
358
  ref: listContainerRef
344
- }, !showViewMore || items.length ? ListContent : EmptyResultView, (0, _react2.jsx)(TypeaheadAssistiveTextPureComponent, {
345
- numberOfResults: items.length.toString()
359
+ }, !showViewMore || itemsLength ? ListContent : EmptyResultView, showViewMore && onViewMoreClick && (0, _react2.jsx)(_ViewMore.ViewMore, {
360
+ onClick: onViewMoreClick,
361
+ isFocused: selectedIndex === itemsLength
362
+ }), (0, _react2.jsx)(TypeaheadAssistiveTextPureComponent, {
363
+ numberOfResults: itemsLength.toString()
346
364
  })));
347
365
  });
348
366
  var TypeAheadList = exports.TypeAheadList = (0, _reactIntlNext.injectIntl)(TypeAheadListComponent);
@@ -20,7 +20,6 @@ var _colors = require("@atlaskit/theme/colors");
20
20
  var _constants = require("../pm-plugins/constants");
21
21
  var _TypeAheadErrorFallback = require("./TypeAheadErrorFallback");
22
22
  var _TypeAheadList = require("./TypeAheadList");
23
- var _ViewMore = require("./ViewMore");
24
23
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
25
24
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
26
25
  /**
@@ -220,22 +219,31 @@ var TypeAheadPopup = exports.TypeAheadPopup = /*#__PURE__*/_react.default.memo(f
220
219
  var _window$getSelection2;
221
220
  // Check if new focus point is inside the current editor. If it is not we
222
221
  // want to close the typeahead popup regardless of text selection state
223
- var focusNode = (_window$getSelection2 = window.getSelection()) === null || _window$getSelection2 === void 0 ? void 0 : _window$getSelection2.focusNode;
222
+ var currentFocus = (_window$getSelection2 = window.getSelection()) === null || _window$getSelection2 === void 0 ? void 0 : _window$getSelection2.focusNode; // the focusNode is either TextNode, ElementNode
223
+ // if currentFocus is not HTMLElement, take its parent node as focusNode
224
+ var focusNode = currentFocus instanceof HTMLElement ? currentFocus : currentFocus === null || currentFocus === void 0 ? void 0 : currentFocus.parentNode;
224
225
  if (focusNode instanceof HTMLElement) {
225
226
  var innerEditor = focusNode.closest('.extension-editable-area');
226
- // When there is no related target, we default to not closing the popup
227
- var newFocusInsideCurrentEditor = !relatedTarget;
228
- if (relatedTarget instanceof HTMLElement) {
229
- if (innerEditor) {
230
- // check if the new focus is inside inner editor, keep popup opens
231
- newFocusInsideCurrentEditor = innerEditor.contains(relatedTarget);
232
- } else {
233
- // if the new focus contains current focus node, the popup won't close
234
- newFocusInsideCurrentEditor = relatedTarget.contains(focusNode);
227
+ if (innerEditor) {
228
+ // When there is no related target, we default to not closing the popup
229
+ var newFocusInsideCurrentEditor = !relatedTarget;
230
+ if (relatedTarget instanceof HTMLElement) {
231
+ if (innerEditor) {
232
+ // check if the new focus is inside inner editor, keep popup opens
233
+ newFocusInsideCurrentEditor = innerEditor.contains(relatedTarget);
234
+ } else {
235
+ // if the new focus contains current focus node, the popup won't close
236
+ newFocusInsideCurrentEditor = relatedTarget.contains(focusNode);
237
+ }
238
+ }
239
+ if (!isTextSelected && newFocusInsideCurrentEditor) {
240
+ return;
241
+ }
242
+ } else {
243
+ // if the current focus in outer editor, keep the existing behaviour, do not close the pop up if text is not selected
244
+ if (!isTextSelected) {
245
+ return;
235
246
  }
236
- }
237
- if (!isTextSelected && newFocusInsideCurrentEditor) {
238
- return;
239
247
  }
240
248
  }
241
249
  } else {
@@ -328,9 +336,8 @@ var TypeAheadPopup = exports.TypeAheadPopup = /*#__PURE__*/_react.default.memo(f
328
336
  triggerHandler: triggerHandler,
329
337
  moreElementsInQuickInsertViewEnabled: moreElementsInQuickInsertViewEnabled,
330
338
  api: api,
331
- showViewMore: showViewMore
332
- }), showViewMore && (0, _react2.jsx)(_ViewMore.ViewMore, {
333
- onClick: onViewMoreClick
339
+ showViewMore: showViewMore,
340
+ onViewMoreClick: onViewMoreClick
334
341
  }))));
335
342
  });
336
343
  TypeAheadPopup.displayName = 'TypeAheadPopup';
@@ -5,7 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.ViewMore = void 0;
8
- var _react = require("@emotion/react");
8
+ var _react = require("react");
9
+ var _react2 = require("@emotion/react");
9
10
  var _reactIntlNext = require("react-intl-next");
10
11
  var _messages = require("@atlaskit/editor-common/messages");
11
12
  var _showMoreHorizontalEditorMore = _interopRequireDefault(require("@atlaskit/icon/core/migration/show-more-horizontal--editor-more"));
@@ -15,25 +16,66 @@ var _colors = require("@atlaskit/theme/colors");
15
16
  * @jsxRuntime classic
16
17
  * @jsx jsx
17
18
  */
19
+
18
20
  // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
19
21
 
20
- var buttonStyles = (0, _react.css)({
22
+ var buttonStyles = (0, _react2.css)({
21
23
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
22
24
  '& > button:hover': {
23
25
  backgroundColor: "var(--ds-background-neutral-subtle-hovered, ".concat(_colors.N30, ")")
26
+ },
27
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
28
+ '& > button:focus': {
29
+ backgroundColor: "var(--ds-background-neutral-subtle-hovered, ".concat(_colors.N30, ")"),
30
+ outline: 'none'
24
31
  }
25
32
  });
26
33
  var ViewMore = exports.ViewMore = function ViewMore(_ref) {
27
- var onClick = _ref.onClick;
34
+ var onClick = _ref.onClick,
35
+ isFocused = _ref.isFocused;
28
36
  var _useIntl = (0, _reactIntlNext.useIntl)(),
29
37
  formatMessage = _useIntl.formatMessage;
30
- return (0, _react.jsx)(_menu.Section, {
38
+ var ref = (0, _react.useRef)(null);
39
+ (0, _react.useEffect)(function () {
40
+ if (isFocused && ref.current) {
41
+ ref.current.focus();
42
+ }
43
+ }, [isFocused]);
44
+ (0, _react.useEffect)(function () {
45
+ if (!ref.current) {
46
+ return;
47
+ }
48
+ var element = ref.current;
49
+ var handleEnter = function handleEnter(e) {
50
+ if (e.key === 'Enter') {
51
+ onClick();
52
+ // Prevent keydown listener in TypeaheadList from handling Enter pressed
53
+ e.stopPropagation();
54
+ } else if (e.key === 'Tab') {
55
+ // TypeaheadList will try to insert selected item on Tab press
56
+ // hence stop propagation to prevent that and treat this as noop
57
+ e.stopPropagation();
58
+ e.preventDefault();
59
+ }
60
+ };
61
+
62
+ // Ignored via go/ees005
63
+ // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
64
+ element === null || element === void 0 || element.addEventListener('keydown', handleEnter);
65
+ return function () {
66
+ // Ignored via go/ees005
67
+ // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
68
+ element === null || element === void 0 || element.removeEventListener('keydown', handleEnter);
69
+ };
70
+ });
71
+ return (0, _react2.jsx)(_menu.Section, {
31
72
  hasSeparator: true
32
- }, (0, _react.jsx)("span", {
73
+ }, (0, _react2.jsx)("span", {
33
74
  css: buttonStyles
34
- }, (0, _react.jsx)(_menu.ButtonItem, {
75
+ }, (0, _react2.jsx)(_menu.ButtonItem, {
76
+ ref: ref,
35
77
  onClick: onClick,
36
- iconBefore: (0, _react.jsx)(_showMoreHorizontalEditorMore.default, {
78
+ iconBefore: (0, _react2.jsx)(_showMoreHorizontalEditorMore.default, {
37
79
  label: ""
38
80
  }),
39
81
  "aria-describedby": formatMessage(_messages.toolbarInsertBlockMessages.viewMore),
@@ -9,6 +9,7 @@ exports.WrapperTypeAhead = void 0;
9
9
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
10
10
  var _react = _interopRequireWildcard(require("react"));
11
11
  var _typeAhead = require("@atlaskit/editor-common/type-ahead");
12
+ var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
12
13
  var _updateQuery = require("../pm-plugins/commands/update-query");
13
14
  var _itemIsDisabled2 = require("../pm-plugins/item-is-disabled");
14
15
  var _utils = require("../pm-plugins/utils");
@@ -31,6 +32,9 @@ var WrapperTypeAhead = exports.WrapperTypeAhead = /*#__PURE__*/_react.default.me
31
32
  reopenQuery = _ref.reopenQuery,
32
33
  onUndoRedo = _ref.onUndoRedo,
33
34
  api = _ref.api;
35
+ // @ts-ignore
36
+ var openElementBrowserModal = triggerHandler === null || triggerHandler === void 0 ? void 0 : triggerHandler.openElementBrowserModal;
37
+ var showViewMore = (triggerHandler === null || triggerHandler === void 0 ? void 0 : triggerHandler.id) === _typeAhead.TypeAheadAvailableNodes.QUICK_INSERT && !!openElementBrowserModal && (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1');
34
38
  var _useState = (0, _react.useState)(false),
35
39
  _useState2 = (0, _slicedToArray2.default)(_useState, 2),
36
40
  closed = _useState2[0],
@@ -41,7 +45,7 @@ var WrapperTypeAhead = exports.WrapperTypeAhead = /*#__PURE__*/_react.default.me
41
45
  setQuery = _useState4[1];
42
46
  var queryRef = (0, _react.useRef)(query);
43
47
  var editorViewRef = (0, _react.useRef)(editorView);
44
- var items = (0, _useLoadItems.useLoadItems)(triggerHandler, editorView, query);
48
+ var items = (0, _useLoadItems.useLoadItems)(triggerHandler, editorView, query, showViewMore);
45
49
  (0, _react.useLayoutEffect)(function () {
46
50
  queryRef.current = query;
47
51
  }, [query]);
@@ -11,7 +11,7 @@ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
11
11
  var _updateListError = require("../../pm-plugins/commands/update-list-error");
12
12
  var _updateListItems = require("../../pm-plugins/commands/update-list-items");
13
13
  var EMPTY_LIST_ITEM = [];
14
- var useLoadItems = exports.useLoadItems = function useLoadItems(triggerHandler, editorView, query) {
14
+ var useLoadItems = exports.useLoadItems = function useLoadItems(triggerHandler, editorView, query, showViewMore) {
15
15
  var _useState = (0, _react.useState)(EMPTY_LIST_ITEM),
16
16
  _useState2 = (0, _slicedToArray2.default)(_useState, 2),
17
17
  items = _useState2[0],
@@ -34,8 +34,11 @@ var useLoadItems = exports.useLoadItems = function useLoadItems(triggerHandler,
34
34
  if (componentIsMounted.current) {
35
35
  setItems(list);
36
36
  }
37
+ var viewMoreItem = {
38
+ title: 'View more'
39
+ };
37
40
  queueMicrotask(function () {
38
- (0, _updateListItems.updateListItem)(list)(view.state, view.dispatch);
41
+ (0, _updateListItems.updateListItem)(showViewMore ? list.concat(viewMoreItem) : list)(view.state, view.dispatch);
39
42
  });
40
43
  }).catch(function (e) {
41
44
  if ((0, _platformFeatureFlags.fg)('platform_editor_offline_editing_ga')) {
@@ -208,22 +208,31 @@ var TypeAheadPopup = exports.TypeAheadPopup = /*#__PURE__*/_react.default.memo(f
208
208
  var _window$getSelection2;
209
209
  // Check if new focus point is inside the current editor. If it is not we
210
210
  // want to close the typeahead popup regardless of text selection state
211
- var focusNode = (_window$getSelection2 = window.getSelection()) === null || _window$getSelection2 === void 0 ? void 0 : _window$getSelection2.focusNode;
211
+ var currentFocus = (_window$getSelection2 = window.getSelection()) === null || _window$getSelection2 === void 0 ? void 0 : _window$getSelection2.focusNode; // the focusNode is either TextNode, ElementNode
212
+ // if currentFocus is not HTMLElement, take its parent node as focusNode
213
+ var focusNode = currentFocus instanceof HTMLElement ? currentFocus : currentFocus === null || currentFocus === void 0 ? void 0 : currentFocus.parentNode;
212
214
  if (focusNode instanceof HTMLElement) {
213
215
  var innerEditor = focusNode.closest('.extension-editable-area');
214
- // When there is no related target, we default to not closing the popup
215
- var newFocusInsideCurrentEditor = !relatedTarget;
216
- if (relatedTarget instanceof HTMLElement) {
217
- if (innerEditor) {
218
- // check if the new focus is inside inner editor, keep popup opens
219
- newFocusInsideCurrentEditor = innerEditor.contains(relatedTarget);
220
- } else {
221
- // if the new focus contains current focus node, the popup won't close
222
- newFocusInsideCurrentEditor = relatedTarget.contains(focusNode);
216
+ if (innerEditor) {
217
+ // When there is no related target, we default to not closing the popup
218
+ var newFocusInsideCurrentEditor = !relatedTarget;
219
+ if (relatedTarget instanceof HTMLElement) {
220
+ if (innerEditor) {
221
+ // check if the new focus is inside inner editor, keep popup opens
222
+ newFocusInsideCurrentEditor = innerEditor.contains(relatedTarget);
223
+ } else {
224
+ // if the new focus contains current focus node, the popup won't close
225
+ newFocusInsideCurrentEditor = relatedTarget.contains(focusNode);
226
+ }
227
+ }
228
+ if (!isTextSelected && newFocusInsideCurrentEditor) {
229
+ return;
230
+ }
231
+ } else {
232
+ // if the current focus in outer editor, keep the existing behaviour, do not close the pop up if text is not selected
233
+ if (!isTextSelected) {
234
+ return;
223
235
  }
224
- }
225
- if (!isTextSelected && newFocusInsideCurrentEditor) {
226
- return;
227
236
  }
228
237
  }
229
238
  } else {
@@ -19,6 +19,7 @@ import { TYPE_AHEAD_DECORATION_ELEMENT_ID } from '../pm-plugins/constants';
19
19
  import { getTypeAheadListAriaLabels, moveSelectedIndex } from '../pm-plugins/utils';
20
20
  import { AssistiveText } from './AssistiveText';
21
21
  import { TypeAheadListItem } from './TypeAheadListItem';
22
+ import { ViewMore } from './ViewMore';
22
23
  const LIST_ITEM_ESTIMATED_HEIGHT = 64;
23
24
  const LIST_WIDTH = 320;
24
25
  const list = css({
@@ -52,7 +53,8 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
52
53
  triggerHandler,
53
54
  moreElementsInQuickInsertViewEnabled,
54
55
  api,
55
- showViewMore
56
+ showViewMore,
57
+ onViewMoreClick
56
58
  }) => {
57
59
  var _decorationElement$qu2;
58
60
  const listRef = useRef();
@@ -63,7 +65,10 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
63
65
  startIndex: 0,
64
66
  stopIndex: 0
65
67
  });
66
- const estimatedHeight = items.length * LIST_ITEM_ESTIMATED_HEIGHT;
68
+
69
+ // Exclude view more item from the count
70
+ const itemsLength = showViewMore ? items.length - 1 : items.length;
71
+ const estimatedHeight = itemsLength * LIST_ITEM_ESTIMATED_HEIGHT;
67
72
  const [height, setHeight] = useState(Math.min(estimatedHeight, fitHeight));
68
73
  const [cache, setCache] = useState(new CellMeasurerCache({
69
74
  fixedWidth: true,
@@ -118,7 +123,10 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
118
123
  // to calculate each height. THen, we can schedule a new frame when this one finishs.
119
124
  requestAnimationFrame(() => {
120
125
  requestAnimationFrame(() => {
121
- const isSelectedItemVisible = selectedIndex >= lastVisibleStartIndex && selectedIndex <= lastVisibleStopIndex;
126
+ const isViewMoreSelected = showViewMore && selectedIndex === itemsLength;
127
+ const isSelectedItemVisible = selectedIndex >= lastVisibleStartIndex && selectedIndex <= lastVisibleStopIndex ||
128
+ // view more is always visible, hence no scrolling
129
+ isViewMoreSelected;
122
130
 
123
131
  //Should scroll to the list item only when the selectedIndex >= 0 and item is not visible
124
132
  if (!isSelectedItemVisible && selectedIndex !== -1) {
@@ -128,7 +136,7 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
128
136
  }
129
137
  });
130
138
  });
131
- }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex]);
139
+ }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex, itemsLength, showViewMore]);
132
140
  const onMouseMove = (event, index) => {
133
141
  event.preventDefault();
134
142
  event.stopPropagation();
@@ -141,7 +149,10 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
141
149
  if (!listRef.current) {
142
150
  return;
143
151
  }
144
- const isSelectedItemVisible = selectedIndex >= lastVisibleStartIndex && selectedIndex <= lastVisibleStopIndex;
152
+ const isViewMoreSelected = showViewMore && selectedIndex === itemsLength;
153
+ const isSelectedItemVisible = selectedIndex >= lastVisibleStartIndex && selectedIndex <= lastVisibleStopIndex ||
154
+ // view more is always visible, hence no scrolling
155
+ isViewMoreSelected;
145
156
 
146
157
  //Should scroll to the list item only when the selectedIndex >= 0 and item is not visible
147
158
  if (!isSelectedItemVisible && selectedIndex !== -1) {
@@ -149,7 +160,7 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
149
160
  } else if (selectedIndex === -1) {
150
161
  listRef.current.scrollToRow(0);
151
162
  }
152
- }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex]);
163
+ }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex, itemsLength, showViewMore]);
153
164
  useLayoutEffect(() => {
154
165
  setCache(new CellMeasurerCache({
155
166
  fixedWidth: true,
@@ -167,13 +178,15 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
167
178
  });
168
179
  }, [items]);
169
180
  useLayoutEffect(() => {
170
- const height = Math.min(items.reduce((prevValue, currentValue, index) => {
181
+ // Exclude view more item from the count
182
+ const itemsToRender = showViewMore ? items.slice(0, -1) : items;
183
+ const height = Math.min(itemsToRender.reduce((prevValue, currentValue, index) => {
171
184
  return prevValue + cache.rowHeight({
172
185
  index: index
173
186
  });
174
187
  }, 0), fitHeight);
175
188
  setHeight(height);
176
- }, [items, cache, fitHeight]);
189
+ }, [items, cache, fitHeight, showViewMore]);
177
190
  useLayoutEffect(() => {
178
191
  if (!listContainerRef.current) {
179
192
  return;
@@ -228,7 +241,7 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
228
241
  // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
229
242
  element === null || element === void 0 ? void 0 : element.removeEventListener('keydown', handleKeyDown);
230
243
  };
231
- }, [editorView.state, focusTargetElement, selectNextItem, selectPreviousItem, selectedIndex, onItemClick, items.length]);
244
+ }, [editorView.state, focusTargetElement, selectNextItem, selectPreviousItem, selectedIndex, onItemClick, itemsLength]);
232
245
  const firstOnlineSupportedRow = useMemo(() => {
233
246
  return items.findIndex(item => item.isDisabledOffline !== true);
234
247
  }, [items]);
@@ -255,7 +268,7 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
255
268
  key: items[index].title,
256
269
  item: currentItem,
257
270
  firstOnlineSupportedIndex: firstOnlineSupportedRow,
258
- itemsLength: items.length,
271
+ itemsLength: itemsLength,
259
272
  itemIndex: index,
260
273
  selectedIndex: selectedIndex,
261
274
  onItemClick: actions.onItemClick,
@@ -285,8 +298,10 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
285
298
  })));
286
299
  const ListContent = jsx(List, {
287
300
  rowRenderer: renderRow,
288
- ref: listRef,
289
- rowCount: items.length,
301
+ ref: listRef
302
+ // Skip rendering the view more button in the list
303
+ ,
304
+ rowCount: itemsLength,
290
305
  rowHeight: cache.rowHeight,
291
306
  onRowsRendered: onItemsRendered,
292
307
  width: LIST_WIDTH,
@@ -320,8 +335,11 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
320
335
  }, jsx("div", {
321
336
  id: menuGroupId,
322
337
  ref: listContainerRef
323
- }, !showViewMore || items.length ? ListContent : EmptyResultView, jsx(TypeaheadAssistiveTextPureComponent, {
324
- numberOfResults: items.length.toString()
338
+ }, !showViewMore || itemsLength ? ListContent : EmptyResultView, showViewMore && onViewMoreClick && jsx(ViewMore, {
339
+ onClick: onViewMoreClick,
340
+ isFocused: selectedIndex === itemsLength
341
+ }), jsx(TypeaheadAssistiveTextPureComponent, {
342
+ numberOfResults: itemsLength.toString()
325
343
  })));
326
344
  });
327
345
  export const TypeAheadList = injectIntl(TypeAheadListComponent);
@@ -17,7 +17,6 @@ import { N0, N50A, N60A } from '@atlaskit/theme/colors';
17
17
  import { CloseSelectionOptions, TYPE_AHEAD_DECORATION_DATA_ATTRIBUTE, TYPE_AHEAD_POPUP_CONTENT_CLASS } from '../pm-plugins/constants';
18
18
  import { TypeAheadErrorFallback } from './TypeAheadErrorFallback';
19
19
  import { TypeAheadList } from './TypeAheadList';
20
- import { ViewMore } from './ViewMore';
21
20
  const DEFAULT_TYPEAHEAD_MENU_HEIGHT = 380;
22
21
  const VIEWMORE_BUTTON_HEIGHT = 53;
23
22
  const DEFAULT_TYPEAHEAD_MENU_HEIGHT_NEW = 480;
@@ -209,22 +208,31 @@ export const TypeAheadPopup = /*#__PURE__*/React.memo(props => {
209
208
  var _window$getSelection2;
210
209
  // Check if new focus point is inside the current editor. If it is not we
211
210
  // want to close the typeahead popup regardless of text selection state
212
- const focusNode = (_window$getSelection2 = window.getSelection()) === null || _window$getSelection2 === void 0 ? void 0 : _window$getSelection2.focusNode;
211
+ const currentFocus = (_window$getSelection2 = window.getSelection()) === null || _window$getSelection2 === void 0 ? void 0 : _window$getSelection2.focusNode; // the focusNode is either TextNode, ElementNode
212
+ // if currentFocus is not HTMLElement, take its parent node as focusNode
213
+ const focusNode = currentFocus instanceof HTMLElement ? currentFocus : currentFocus === null || currentFocus === void 0 ? void 0 : currentFocus.parentNode;
213
214
  if (focusNode instanceof HTMLElement) {
214
215
  const innerEditor = focusNode.closest('.extension-editable-area');
215
- // When there is no related target, we default to not closing the popup
216
- let newFocusInsideCurrentEditor = !relatedTarget;
217
- if (relatedTarget instanceof HTMLElement) {
218
- if (innerEditor) {
219
- // check if the new focus is inside inner editor, keep popup opens
220
- newFocusInsideCurrentEditor = innerEditor.contains(relatedTarget);
221
- } else {
222
- // if the new focus contains current focus node, the popup won't close
223
- newFocusInsideCurrentEditor = relatedTarget.contains(focusNode);
216
+ if (innerEditor) {
217
+ // When there is no related target, we default to not closing the popup
218
+ let newFocusInsideCurrentEditor = !relatedTarget;
219
+ if (relatedTarget instanceof HTMLElement) {
220
+ if (innerEditor) {
221
+ // check if the new focus is inside inner editor, keep popup opens
222
+ newFocusInsideCurrentEditor = innerEditor.contains(relatedTarget);
223
+ } else {
224
+ // if the new focus contains current focus node, the popup won't close
225
+ newFocusInsideCurrentEditor = relatedTarget.contains(focusNode);
226
+ }
227
+ }
228
+ if (!isTextSelected && newFocusInsideCurrentEditor) {
229
+ return;
230
+ }
231
+ } else {
232
+ // if the current focus in outer editor, keep the existing behaviour, do not close the pop up if text is not selected
233
+ if (!isTextSelected) {
234
+ return;
224
235
  }
225
- }
226
- if (!isTextSelected && newFocusInsideCurrentEditor) {
227
- return;
228
236
  }
229
237
  }
230
238
  } else {
@@ -321,9 +329,8 @@ export const TypeAheadPopup = /*#__PURE__*/React.memo(props => {
321
329
  triggerHandler: triggerHandler,
322
330
  moreElementsInQuickInsertViewEnabled: moreElementsInQuickInsertViewEnabled,
323
331
  api: api,
324
- showViewMore: showViewMore
325
- }), showViewMore && jsx(ViewMore, {
326
- onClick: onViewMoreClick
332
+ showViewMore: showViewMore,
333
+ onViewMoreClick: onViewMoreClick
327
334
  }))));
328
335
  });
329
336
  TypeAheadPopup.displayName = 'TypeAheadPopup';
@@ -2,6 +2,8 @@
2
2
  * @jsxRuntime classic
3
3
  * @jsx jsx
4
4
  */
5
+ import { useEffect, useRef } from 'react';
6
+
5
7
  // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
6
8
  import { css, jsx } from '@emotion/react';
7
9
  import { useIntl } from 'react-intl-next';
@@ -13,19 +15,61 @@ const buttonStyles = css({
13
15
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
14
16
  '& > button:hover': {
15
17
  backgroundColor: `var(--ds-background-neutral-subtle-hovered, ${N30})`
18
+ },
19
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
20
+ '& > button:focus': {
21
+ backgroundColor: `var(--ds-background-neutral-subtle-hovered, ${N30})`,
22
+ outline: 'none'
16
23
  }
17
24
  });
18
25
  export const ViewMore = ({
19
- onClick
26
+ onClick,
27
+ isFocused
20
28
  }) => {
21
29
  const {
22
30
  formatMessage
23
31
  } = useIntl();
32
+ const ref = useRef(null);
33
+ useEffect(() => {
34
+ if (isFocused && ref.current) {
35
+ ref.current.focus();
36
+ }
37
+ }, [isFocused]);
38
+ useEffect(() => {
39
+ if (!ref.current) {
40
+ return;
41
+ }
42
+ const {
43
+ current: element
44
+ } = ref;
45
+ const handleEnter = e => {
46
+ if (e.key === 'Enter') {
47
+ onClick();
48
+ // Prevent keydown listener in TypeaheadList from handling Enter pressed
49
+ e.stopPropagation();
50
+ } else if (e.key === 'Tab') {
51
+ // TypeaheadList will try to insert selected item on Tab press
52
+ // hence stop propagation to prevent that and treat this as noop
53
+ e.stopPropagation();
54
+ e.preventDefault();
55
+ }
56
+ };
57
+
58
+ // Ignored via go/ees005
59
+ // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
60
+ element === null || element === void 0 ? void 0 : element.addEventListener('keydown', handleEnter);
61
+ return () => {
62
+ // Ignored via go/ees005
63
+ // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
64
+ element === null || element === void 0 ? void 0 : element.removeEventListener('keydown', handleEnter);
65
+ };
66
+ });
24
67
  return jsx(Section, {
25
68
  hasSeparator: true
26
69
  }, jsx("span", {
27
70
  css: buttonStyles
28
71
  }, jsx(ButtonItem, {
72
+ ref: ref,
29
73
  onClick: onClick,
30
74
  iconBefore: jsx(ShowMoreHorizontalIcon, {
31
75
  label: ""
@@ -1,5 +1,6 @@
1
1
  import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
- import { SelectItemMode } from '@atlaskit/editor-common/type-ahead';
2
+ import { SelectItemMode, TypeAheadAvailableNodes } from '@atlaskit/editor-common/type-ahead';
3
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
3
4
  import { updateQuery } from '../pm-plugins/commands/update-query';
4
5
  import { itemIsDisabled } from '../pm-plugins/item-is-disabled';
5
6
  import { getPluginState, moveSelectedIndex, skipForwardToSafeItem } from '../pm-plugins/utils';
@@ -21,11 +22,14 @@ export const WrapperTypeAhead = /*#__PURE__*/React.memo(({
21
22
  onUndoRedo,
22
23
  api
23
24
  }) => {
25
+ // @ts-ignore
26
+ const openElementBrowserModal = triggerHandler === null || triggerHandler === void 0 ? void 0 : triggerHandler.openElementBrowserModal;
27
+ const showViewMore = (triggerHandler === null || triggerHandler === void 0 ? void 0 : triggerHandler.id) === TypeAheadAvailableNodes.QUICK_INSERT && !!openElementBrowserModal && editorExperiment('platform_editor_controls', 'variant1');
24
28
  const [closed, setClosed] = useState(false);
25
29
  const [query, setQuery] = useState(reopenQuery || '');
26
30
  const queryRef = useRef(query);
27
31
  const editorViewRef = useRef(editorView);
28
- const items = useLoadItems(triggerHandler, editorView, query);
32
+ const items = useLoadItems(triggerHandler, editorView, query, showViewMore);
29
33
  useLayoutEffect(() => {
30
34
  queryRef.current = query;
31
35
  }, [query]);
@@ -3,7 +3,7 @@ import { fg } from '@atlaskit/platform-feature-flags';
3
3
  import { updateListError } from '../../pm-plugins/commands/update-list-error';
4
4
  import { updateListItem } from '../../pm-plugins/commands/update-list-items';
5
5
  const EMPTY_LIST_ITEM = [];
6
- export const useLoadItems = (triggerHandler, editorView, query) => {
6
+ export const useLoadItems = (triggerHandler, editorView, query, showViewMore) => {
7
7
  const [items, setItems] = useState(EMPTY_LIST_ITEM);
8
8
  const componentIsMounted = useRef(true);
9
9
  const editorViewRef = useRef(editorView);
@@ -25,8 +25,11 @@ export const useLoadItems = (triggerHandler, editorView, query) => {
25
25
  if (componentIsMounted.current) {
26
26
  setItems(list);
27
27
  }
28
+ const viewMoreItem = {
29
+ title: 'View more'
30
+ };
28
31
  queueMicrotask(() => {
29
- updateListItem(list)(view.state, view.dispatch);
32
+ updateListItem(showViewMore ? list.concat(viewMoreItem) : list)(view.state, view.dispatch);
30
33
  });
31
34
  }).catch(e => {
32
35
  if (fg('platform_editor_offline_editing_ga')) {
@@ -199,22 +199,31 @@ export const TypeAheadPopup = /*#__PURE__*/React.memo(props => {
199
199
  var _window$getSelection2;
200
200
  // Check if new focus point is inside the current editor. If it is not we
201
201
  // want to close the typeahead popup regardless of text selection state
202
- const focusNode = (_window$getSelection2 = window.getSelection()) === null || _window$getSelection2 === void 0 ? void 0 : _window$getSelection2.focusNode;
202
+ const currentFocus = (_window$getSelection2 = window.getSelection()) === null || _window$getSelection2 === void 0 ? void 0 : _window$getSelection2.focusNode; // the focusNode is either TextNode, ElementNode
203
+ // if currentFocus is not HTMLElement, take its parent node as focusNode
204
+ const focusNode = currentFocus instanceof HTMLElement ? currentFocus : currentFocus === null || currentFocus === void 0 ? void 0 : currentFocus.parentNode;
203
205
  if (focusNode instanceof HTMLElement) {
204
206
  const innerEditor = focusNode.closest('.extension-editable-area');
205
- // When there is no related target, we default to not closing the popup
206
- let newFocusInsideCurrentEditor = !relatedTarget;
207
- if (relatedTarget instanceof HTMLElement) {
208
- if (innerEditor) {
209
- // check if the new focus is inside inner editor, keep popup opens
210
- newFocusInsideCurrentEditor = innerEditor.contains(relatedTarget);
211
- } else {
212
- // if the new focus contains current focus node, the popup won't close
213
- newFocusInsideCurrentEditor = relatedTarget.contains(focusNode);
207
+ if (innerEditor) {
208
+ // When there is no related target, we default to not closing the popup
209
+ let newFocusInsideCurrentEditor = !relatedTarget;
210
+ if (relatedTarget instanceof HTMLElement) {
211
+ if (innerEditor) {
212
+ // check if the new focus is inside inner editor, keep popup opens
213
+ newFocusInsideCurrentEditor = innerEditor.contains(relatedTarget);
214
+ } else {
215
+ // if the new focus contains current focus node, the popup won't close
216
+ newFocusInsideCurrentEditor = relatedTarget.contains(focusNode);
217
+ }
218
+ }
219
+ if (!isTextSelected && newFocusInsideCurrentEditor) {
220
+ return;
221
+ }
222
+ } else {
223
+ // if the current focus in outer editor, keep the existing behaviour, do not close the pop up if text is not selected
224
+ if (!isTextSelected) {
225
+ return;
214
226
  }
215
- }
216
- if (!isTextSelected && newFocusInsideCurrentEditor) {
217
- return;
218
227
  }
219
228
  }
220
229
  } else {
@@ -20,6 +20,7 @@ import { TYPE_AHEAD_DECORATION_ELEMENT_ID } from '../pm-plugins/constants';
20
20
  import { getTypeAheadListAriaLabels, moveSelectedIndex } from '../pm-plugins/utils';
21
21
  import { AssistiveText } from './AssistiveText';
22
22
  import { TypeAheadListItem } from './TypeAheadListItem';
23
+ import { ViewMore } from './ViewMore';
23
24
  var LIST_ITEM_ESTIMATED_HEIGHT = 64;
24
25
  var LIST_WIDTH = 320;
25
26
  var list = css({
@@ -53,7 +54,8 @@ var TypeAheadListComponent = /*#__PURE__*/React.memo(function (_ref2) {
53
54
  triggerHandler = _ref2.triggerHandler,
54
55
  moreElementsInQuickInsertViewEnabled = _ref2.moreElementsInQuickInsertViewEnabled,
55
56
  api = _ref2.api,
56
- showViewMore = _ref2.showViewMore;
57
+ showViewMore = _ref2.showViewMore,
58
+ onViewMoreClick = _ref2.onViewMoreClick;
57
59
  var listRef = useRef();
58
60
  var listContainerRef = useRef(null);
59
61
  var lastVisibleIndexes = useRef({
@@ -62,7 +64,10 @@ var TypeAheadListComponent = /*#__PURE__*/React.memo(function (_ref2) {
62
64
  startIndex: 0,
63
65
  stopIndex: 0
64
66
  });
65
- var estimatedHeight = items.length * LIST_ITEM_ESTIMATED_HEIGHT;
67
+
68
+ // Exclude view more item from the count
69
+ var itemsLength = showViewMore ? items.length - 1 : items.length;
70
+ var estimatedHeight = itemsLength * LIST_ITEM_ESTIMATED_HEIGHT;
66
71
  var _useState = useState(Math.min(estimatedHeight, fitHeight)),
67
72
  _useState2 = _slicedToArray(_useState, 2),
68
73
  height = _useState2[0],
@@ -128,7 +133,10 @@ var TypeAheadListComponent = /*#__PURE__*/React.memo(function (_ref2) {
128
133
  // to calculate each height. THen, we can schedule a new frame when this one finishs.
129
134
  requestAnimationFrame(function () {
130
135
  requestAnimationFrame(function () {
131
- var isSelectedItemVisible = selectedIndex >= lastVisibleStartIndex && selectedIndex <= lastVisibleStopIndex;
136
+ var isViewMoreSelected = showViewMore && selectedIndex === itemsLength;
137
+ var isSelectedItemVisible = selectedIndex >= lastVisibleStartIndex && selectedIndex <= lastVisibleStopIndex ||
138
+ // view more is always visible, hence no scrolling
139
+ isViewMoreSelected;
132
140
 
133
141
  //Should scroll to the list item only when the selectedIndex >= 0 and item is not visible
134
142
  if (!isSelectedItemVisible && selectedIndex !== -1) {
@@ -138,7 +146,7 @@ var TypeAheadListComponent = /*#__PURE__*/React.memo(function (_ref2) {
138
146
  }
139
147
  });
140
148
  });
141
- }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex]);
149
+ }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex, itemsLength, showViewMore]);
142
150
  var _onMouseMove = function onMouseMove(event, index) {
143
151
  event.preventDefault();
144
152
  event.stopPropagation();
@@ -151,7 +159,10 @@ var TypeAheadListComponent = /*#__PURE__*/React.memo(function (_ref2) {
151
159
  if (!listRef.current) {
152
160
  return;
153
161
  }
154
- var isSelectedItemVisible = selectedIndex >= lastVisibleStartIndex && selectedIndex <= lastVisibleStopIndex;
162
+ var isViewMoreSelected = showViewMore && selectedIndex === itemsLength;
163
+ var isSelectedItemVisible = selectedIndex >= lastVisibleStartIndex && selectedIndex <= lastVisibleStopIndex ||
164
+ // view more is always visible, hence no scrolling
165
+ isViewMoreSelected;
155
166
 
156
167
  //Should scroll to the list item only when the selectedIndex >= 0 and item is not visible
157
168
  if (!isSelectedItemVisible && selectedIndex !== -1) {
@@ -159,7 +170,7 @@ var TypeAheadListComponent = /*#__PURE__*/React.memo(function (_ref2) {
159
170
  } else if (selectedIndex === -1) {
160
171
  listRef.current.scrollToRow(0);
161
172
  }
162
- }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex]);
173
+ }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex, itemsLength, showViewMore]);
163
174
  useLayoutEffect(function () {
164
175
  setCache(new CellMeasurerCache({
165
176
  fixedWidth: true,
@@ -177,13 +188,15 @@ var TypeAheadListComponent = /*#__PURE__*/React.memo(function (_ref2) {
177
188
  });
178
189
  }, [items]);
179
190
  useLayoutEffect(function () {
180
- var height = Math.min(items.reduce(function (prevValue, currentValue, index) {
191
+ // Exclude view more item from the count
192
+ var itemsToRender = showViewMore ? items.slice(0, -1) : items;
193
+ var height = Math.min(itemsToRender.reduce(function (prevValue, currentValue, index) {
181
194
  return prevValue + cache.rowHeight({
182
195
  index: index
183
196
  });
184
197
  }, 0), fitHeight);
185
198
  setHeight(height);
186
- }, [items, cache, fitHeight]);
199
+ }, [items, cache, fitHeight, showViewMore]);
187
200
  useLayoutEffect(function () {
188
201
  if (!listContainerRef.current) {
189
202
  return;
@@ -236,7 +249,7 @@ var TypeAheadListComponent = /*#__PURE__*/React.memo(function (_ref2) {
236
249
  // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
237
250
  element === null || element === void 0 || element.removeEventListener('keydown', handleKeyDown);
238
251
  };
239
- }, [editorView.state, focusTargetElement, selectNextItem, selectPreviousItem, selectedIndex, onItemClick, items.length]);
252
+ }, [editorView.state, focusTargetElement, selectNextItem, selectPreviousItem, selectedIndex, onItemClick, itemsLength]);
240
253
  var firstOnlineSupportedRow = useMemo(function () {
241
254
  return items.findIndex(function (item) {
242
255
  return item.isDisabledOffline !== true;
@@ -266,7 +279,7 @@ var TypeAheadListComponent = /*#__PURE__*/React.memo(function (_ref2) {
266
279
  key: items[index].title,
267
280
  item: currentItem,
268
281
  firstOnlineSupportedIndex: firstOnlineSupportedRow,
269
- itemsLength: items.length,
282
+ itemsLength: itemsLength,
270
283
  itemIndex: index,
271
284
  selectedIndex: selectedIndex,
272
285
  onItemClick: actions.onItemClick,
@@ -296,8 +309,10 @@ var TypeAheadListComponent = /*#__PURE__*/React.memo(function (_ref2) {
296
309
  })));
297
310
  var ListContent = jsx(List, {
298
311
  rowRenderer: renderRow,
299
- ref: listRef,
300
- rowCount: items.length,
312
+ ref: listRef
313
+ // Skip rendering the view more button in the list
314
+ ,
315
+ rowCount: itemsLength,
301
316
  rowHeight: cache.rowHeight,
302
317
  onRowsRendered: onItemsRendered,
303
318
  width: LIST_WIDTH,
@@ -331,8 +346,11 @@ var TypeAheadListComponent = /*#__PURE__*/React.memo(function (_ref2) {
331
346
  }, jsx("div", {
332
347
  id: menuGroupId,
333
348
  ref: listContainerRef
334
- }, !showViewMore || items.length ? ListContent : EmptyResultView, jsx(TypeaheadAssistiveTextPureComponent, {
335
- numberOfResults: items.length.toString()
349
+ }, !showViewMore || itemsLength ? ListContent : EmptyResultView, showViewMore && onViewMoreClick && jsx(ViewMore, {
350
+ onClick: onViewMoreClick,
351
+ isFocused: selectedIndex === itemsLength
352
+ }), jsx(TypeaheadAssistiveTextPureComponent, {
353
+ numberOfResults: itemsLength.toString()
336
354
  })));
337
355
  });
338
356
  export var TypeAheadList = injectIntl(TypeAheadListComponent);
@@ -18,7 +18,6 @@ import { N0, N50A, N60A } from '@atlaskit/theme/colors';
18
18
  import { CloseSelectionOptions, TYPE_AHEAD_DECORATION_DATA_ATTRIBUTE, TYPE_AHEAD_POPUP_CONTENT_CLASS } from '../pm-plugins/constants';
19
19
  import { TypeAheadErrorFallback } from './TypeAheadErrorFallback';
20
20
  import { TypeAheadList } from './TypeAheadList';
21
- import { ViewMore } from './ViewMore';
22
21
  var DEFAULT_TYPEAHEAD_MENU_HEIGHT = 380;
23
22
  var VIEWMORE_BUTTON_HEIGHT = 53;
24
23
  var DEFAULT_TYPEAHEAD_MENU_HEIGHT_NEW = 480;
@@ -209,22 +208,31 @@ export var TypeAheadPopup = /*#__PURE__*/React.memo(function (props) {
209
208
  var _window$getSelection2;
210
209
  // Check if new focus point is inside the current editor. If it is not we
211
210
  // want to close the typeahead popup regardless of text selection state
212
- var focusNode = (_window$getSelection2 = window.getSelection()) === null || _window$getSelection2 === void 0 ? void 0 : _window$getSelection2.focusNode;
211
+ var currentFocus = (_window$getSelection2 = window.getSelection()) === null || _window$getSelection2 === void 0 ? void 0 : _window$getSelection2.focusNode; // the focusNode is either TextNode, ElementNode
212
+ // if currentFocus is not HTMLElement, take its parent node as focusNode
213
+ var focusNode = currentFocus instanceof HTMLElement ? currentFocus : currentFocus === null || currentFocus === void 0 ? void 0 : currentFocus.parentNode;
213
214
  if (focusNode instanceof HTMLElement) {
214
215
  var innerEditor = focusNode.closest('.extension-editable-area');
215
- // When there is no related target, we default to not closing the popup
216
- var newFocusInsideCurrentEditor = !relatedTarget;
217
- if (relatedTarget instanceof HTMLElement) {
218
- if (innerEditor) {
219
- // check if the new focus is inside inner editor, keep popup opens
220
- newFocusInsideCurrentEditor = innerEditor.contains(relatedTarget);
221
- } else {
222
- // if the new focus contains current focus node, the popup won't close
223
- newFocusInsideCurrentEditor = relatedTarget.contains(focusNode);
216
+ if (innerEditor) {
217
+ // When there is no related target, we default to not closing the popup
218
+ var newFocusInsideCurrentEditor = !relatedTarget;
219
+ if (relatedTarget instanceof HTMLElement) {
220
+ if (innerEditor) {
221
+ // check if the new focus is inside inner editor, keep popup opens
222
+ newFocusInsideCurrentEditor = innerEditor.contains(relatedTarget);
223
+ } else {
224
+ // if the new focus contains current focus node, the popup won't close
225
+ newFocusInsideCurrentEditor = relatedTarget.contains(focusNode);
226
+ }
227
+ }
228
+ if (!isTextSelected && newFocusInsideCurrentEditor) {
229
+ return;
230
+ }
231
+ } else {
232
+ // if the current focus in outer editor, keep the existing behaviour, do not close the pop up if text is not selected
233
+ if (!isTextSelected) {
234
+ return;
224
235
  }
225
- }
226
- if (!isTextSelected && newFocusInsideCurrentEditor) {
227
- return;
228
236
  }
229
237
  }
230
238
  } else {
@@ -317,9 +325,8 @@ export var TypeAheadPopup = /*#__PURE__*/React.memo(function (props) {
317
325
  triggerHandler: triggerHandler,
318
326
  moreElementsInQuickInsertViewEnabled: moreElementsInQuickInsertViewEnabled,
319
327
  api: api,
320
- showViewMore: showViewMore
321
- }), showViewMore && jsx(ViewMore, {
322
- onClick: onViewMoreClick
328
+ showViewMore: showViewMore,
329
+ onViewMoreClick: onViewMoreClick
323
330
  }))));
324
331
  });
325
332
  TypeAheadPopup.displayName = 'TypeAheadPopup';
@@ -2,6 +2,8 @@
2
2
  * @jsxRuntime classic
3
3
  * @jsx jsx
4
4
  */
5
+ import { useEffect, useRef } from 'react';
6
+
5
7
  // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
6
8
  import { css, jsx } from '@emotion/react';
7
9
  import { useIntl } from 'react-intl-next';
@@ -13,17 +15,57 @@ var buttonStyles = css({
13
15
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
14
16
  '& > button:hover': {
15
17
  backgroundColor: "var(--ds-background-neutral-subtle-hovered, ".concat(N30, ")")
18
+ },
19
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
20
+ '& > button:focus': {
21
+ backgroundColor: "var(--ds-background-neutral-subtle-hovered, ".concat(N30, ")"),
22
+ outline: 'none'
16
23
  }
17
24
  });
18
25
  export var ViewMore = function ViewMore(_ref) {
19
- var onClick = _ref.onClick;
26
+ var onClick = _ref.onClick,
27
+ isFocused = _ref.isFocused;
20
28
  var _useIntl = useIntl(),
21
29
  formatMessage = _useIntl.formatMessage;
30
+ var ref = useRef(null);
31
+ useEffect(function () {
32
+ if (isFocused && ref.current) {
33
+ ref.current.focus();
34
+ }
35
+ }, [isFocused]);
36
+ useEffect(function () {
37
+ if (!ref.current) {
38
+ return;
39
+ }
40
+ var element = ref.current;
41
+ var handleEnter = function handleEnter(e) {
42
+ if (e.key === 'Enter') {
43
+ onClick();
44
+ // Prevent keydown listener in TypeaheadList from handling Enter pressed
45
+ e.stopPropagation();
46
+ } else if (e.key === 'Tab') {
47
+ // TypeaheadList will try to insert selected item on Tab press
48
+ // hence stop propagation to prevent that and treat this as noop
49
+ e.stopPropagation();
50
+ e.preventDefault();
51
+ }
52
+ };
53
+
54
+ // Ignored via go/ees005
55
+ // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
56
+ element === null || element === void 0 || element.addEventListener('keydown', handleEnter);
57
+ return function () {
58
+ // Ignored via go/ees005
59
+ // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
60
+ element === null || element === void 0 || element.removeEventListener('keydown', handleEnter);
61
+ };
62
+ });
22
63
  return jsx(Section, {
23
64
  hasSeparator: true
24
65
  }, jsx("span", {
25
66
  css: buttonStyles
26
67
  }, jsx(ButtonItem, {
68
+ ref: ref,
27
69
  onClick: onClick,
28
70
  iconBefore: jsx(ShowMoreHorizontalIcon, {
29
71
  label: ""
@@ -1,6 +1,7 @@
1
1
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
2
  import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
3
- import { SelectItemMode } from '@atlaskit/editor-common/type-ahead';
3
+ import { SelectItemMode, TypeAheadAvailableNodes } from '@atlaskit/editor-common/type-ahead';
4
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
4
5
  import { updateQuery } from '../pm-plugins/commands/update-query';
5
6
  import { itemIsDisabled as _itemIsDisabled } from '../pm-plugins/item-is-disabled';
6
7
  import { getPluginState, moveSelectedIndex, skipForwardToSafeItem } from '../pm-plugins/utils';
@@ -21,6 +22,9 @@ export var WrapperTypeAhead = /*#__PURE__*/React.memo(function (_ref) {
21
22
  reopenQuery = _ref.reopenQuery,
22
23
  onUndoRedo = _ref.onUndoRedo,
23
24
  api = _ref.api;
25
+ // @ts-ignore
26
+ var openElementBrowserModal = triggerHandler === null || triggerHandler === void 0 ? void 0 : triggerHandler.openElementBrowserModal;
27
+ var showViewMore = (triggerHandler === null || triggerHandler === void 0 ? void 0 : triggerHandler.id) === TypeAheadAvailableNodes.QUICK_INSERT && !!openElementBrowserModal && editorExperiment('platform_editor_controls', 'variant1');
24
28
  var _useState = useState(false),
25
29
  _useState2 = _slicedToArray(_useState, 2),
26
30
  closed = _useState2[0],
@@ -31,7 +35,7 @@ export var WrapperTypeAhead = /*#__PURE__*/React.memo(function (_ref) {
31
35
  setQuery = _useState4[1];
32
36
  var queryRef = useRef(query);
33
37
  var editorViewRef = useRef(editorView);
34
- var items = useLoadItems(triggerHandler, editorView, query);
38
+ var items = useLoadItems(triggerHandler, editorView, query, showViewMore);
35
39
  useLayoutEffect(function () {
36
40
  queryRef.current = query;
37
41
  }, [query]);
@@ -4,7 +4,7 @@ import { fg } from '@atlaskit/platform-feature-flags';
4
4
  import { updateListError } from '../../pm-plugins/commands/update-list-error';
5
5
  import { updateListItem } from '../../pm-plugins/commands/update-list-items';
6
6
  var EMPTY_LIST_ITEM = [];
7
- export var useLoadItems = function useLoadItems(triggerHandler, editorView, query) {
7
+ export var useLoadItems = function useLoadItems(triggerHandler, editorView, query, showViewMore) {
8
8
  var _useState = useState(EMPTY_LIST_ITEM),
9
9
  _useState2 = _slicedToArray(_useState, 2),
10
10
  items = _useState2[0],
@@ -27,8 +27,11 @@ export var useLoadItems = function useLoadItems(triggerHandler, editorView, quer
27
27
  if (componentIsMounted.current) {
28
28
  setItems(list);
29
29
  }
30
+ var viewMoreItem = {
31
+ title: 'View more'
32
+ };
30
33
  queueMicrotask(function () {
31
- updateListItem(list)(view.state, view.dispatch);
34
+ updateListItem(showViewMore ? list.concat(viewMoreItem) : list)(view.state, view.dispatch);
32
35
  });
33
36
  }).catch(function (e) {
34
37
  if (fg('platform_editor_offline_editing_ga')) {
@@ -198,22 +198,31 @@ export var TypeAheadPopup = /*#__PURE__*/React.memo(function (props) {
198
198
  var _window$getSelection2;
199
199
  // Check if new focus point is inside the current editor. If it is not we
200
200
  // want to close the typeahead popup regardless of text selection state
201
- var focusNode = (_window$getSelection2 = window.getSelection()) === null || _window$getSelection2 === void 0 ? void 0 : _window$getSelection2.focusNode;
201
+ var currentFocus = (_window$getSelection2 = window.getSelection()) === null || _window$getSelection2 === void 0 ? void 0 : _window$getSelection2.focusNode; // the focusNode is either TextNode, ElementNode
202
+ // if currentFocus is not HTMLElement, take its parent node as focusNode
203
+ var focusNode = currentFocus instanceof HTMLElement ? currentFocus : currentFocus === null || currentFocus === void 0 ? void 0 : currentFocus.parentNode;
202
204
  if (focusNode instanceof HTMLElement) {
203
205
  var innerEditor = focusNode.closest('.extension-editable-area');
204
- // When there is no related target, we default to not closing the popup
205
- var newFocusInsideCurrentEditor = !relatedTarget;
206
- if (relatedTarget instanceof HTMLElement) {
207
- if (innerEditor) {
208
- // check if the new focus is inside inner editor, keep popup opens
209
- newFocusInsideCurrentEditor = innerEditor.contains(relatedTarget);
210
- } else {
211
- // if the new focus contains current focus node, the popup won't close
212
- newFocusInsideCurrentEditor = relatedTarget.contains(focusNode);
206
+ if (innerEditor) {
207
+ // When there is no related target, we default to not closing the popup
208
+ var newFocusInsideCurrentEditor = !relatedTarget;
209
+ if (relatedTarget instanceof HTMLElement) {
210
+ if (innerEditor) {
211
+ // check if the new focus is inside inner editor, keep popup opens
212
+ newFocusInsideCurrentEditor = innerEditor.contains(relatedTarget);
213
+ } else {
214
+ // if the new focus contains current focus node, the popup won't close
215
+ newFocusInsideCurrentEditor = relatedTarget.contains(focusNode);
216
+ }
217
+ }
218
+ if (!isTextSelected && newFocusInsideCurrentEditor) {
219
+ return;
220
+ }
221
+ } else {
222
+ // if the current focus in outer editor, keep the existing behaviour, do not close the pop up if text is not selected
223
+ if (!isTextSelected) {
224
+ return;
213
225
  }
214
- }
215
- if (!isTextSelected && newFocusInsideCurrentEditor) {
216
- return;
217
226
  }
218
227
  }
219
228
  } else {
@@ -20,6 +20,7 @@ export declare const TypeAheadList: React.FC<import("react-intl-next").WithIntlP
20
20
  moreElementsInQuickInsertViewEnabled?: boolean | undefined;
21
21
  api: ExtractInjectionAPI<TypeAheadPlugin> | undefined;
22
22
  showViewMore?: boolean | undefined;
23
+ onViewMoreClick?: (() => void) | undefined;
23
24
  } & WrappedComponentProps>> & {
24
25
  WrappedComponent: React.ComponentType<{
25
26
  items: Array<TypeAheadItem>;
@@ -32,5 +33,6 @@ export declare const TypeAheadList: React.FC<import("react-intl-next").WithIntlP
32
33
  moreElementsInQuickInsertViewEnabled?: boolean | undefined;
33
34
  api: ExtractInjectionAPI<TypeAheadPlugin> | undefined;
34
35
  showViewMore?: boolean | undefined;
36
+ onViewMoreClick?: (() => void) | undefined;
35
37
  } & WrappedComponentProps>;
36
38
  };
@@ -1,8 +1,5 @@
1
- /**
2
- * @jsxRuntime classic
3
- * @jsx jsx
4
- */
5
1
  import { jsx } from '@emotion/react';
6
- export declare const ViewMore: ({ onClick, }: {
7
- onClick: (e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => void;
2
+ export declare const ViewMore: ({ onClick, isFocused }: {
3
+ onClick: () => void;
4
+ isFocused: boolean;
8
5
  }) => jsx.JSX.Element;
@@ -1,3 +1,3 @@
1
1
  import type { TypeAheadHandler, TypeAheadItem } from '@atlaskit/editor-common/types';
2
2
  import type { EditorView } from '@atlaskit/editor-prosemirror/view';
3
- export declare const useLoadItems: (triggerHandler: TypeAheadHandler, editorView: EditorView, query: string) => Array<TypeAheadItem>;
3
+ export declare const useLoadItems: (triggerHandler: TypeAheadHandler, editorView: EditorView, query: string, showViewMore?: boolean) => Array<TypeAheadItem>;
@@ -20,6 +20,7 @@ export declare const TypeAheadList: React.FC<import("react-intl-next").WithIntlP
20
20
  moreElementsInQuickInsertViewEnabled?: boolean | undefined;
21
21
  api: ExtractInjectionAPI<TypeAheadPlugin> | undefined;
22
22
  showViewMore?: boolean | undefined;
23
+ onViewMoreClick?: (() => void) | undefined;
23
24
  } & WrappedComponentProps>> & {
24
25
  WrappedComponent: React.ComponentType<{
25
26
  items: Array<TypeAheadItem>;
@@ -32,5 +33,6 @@ export declare const TypeAheadList: React.FC<import("react-intl-next").WithIntlP
32
33
  moreElementsInQuickInsertViewEnabled?: boolean | undefined;
33
34
  api: ExtractInjectionAPI<TypeAheadPlugin> | undefined;
34
35
  showViewMore?: boolean | undefined;
36
+ onViewMoreClick?: (() => void) | undefined;
35
37
  } & WrappedComponentProps>;
36
38
  };
@@ -1,8 +1,5 @@
1
- /**
2
- * @jsxRuntime classic
3
- * @jsx jsx
4
- */
5
1
  import { jsx } from '@emotion/react';
6
- export declare const ViewMore: ({ onClick, }: {
7
- onClick: (e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => void;
2
+ export declare const ViewMore: ({ onClick, isFocused }: {
3
+ onClick: () => void;
4
+ isFocused: boolean;
8
5
  }) => jsx.JSX.Element;
@@ -1,3 +1,3 @@
1
1
  import type { TypeAheadHandler, TypeAheadItem } from '@atlaskit/editor-common/types';
2
2
  import type { EditorView } from '@atlaskit/editor-prosemirror/view';
3
- export declare const useLoadItems: (triggerHandler: TypeAheadHandler, editorView: EditorView, query: string) => Array<TypeAheadItem>;
3
+ export declare const useLoadItems: (triggerHandler: TypeAheadHandler, editorView: EditorView, query: string, showViewMore?: boolean) => Array<TypeAheadItem>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-type-ahead",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "description": "Type-ahead plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@atlaskit/adf-schema": "^47.6.0",
37
- "@atlaskit/editor-common": "^101.0.0",
37
+ "@atlaskit/editor-common": "^101.1.0",
38
38
  "@atlaskit/editor-element-browser": "^0.1.0",
39
39
  "@atlaskit/editor-plugin-analytics": "^2.1.0",
40
40
  "@atlaskit/editor-plugin-connectivity": "^2.0.0",
@@ -47,7 +47,7 @@
47
47
  "@atlaskit/platform-feature-flags": "^1.1.0",
48
48
  "@atlaskit/primitives": "^14.1.0",
49
49
  "@atlaskit/prosemirror-input-rules": "^3.3.0",
50
- "@atlaskit/theme": "^17.0.0",
50
+ "@atlaskit/theme": "^18.0.0",
51
51
  "@atlaskit/tmp-editor-statsig": "^3.4.0",
52
52
  "@atlaskit/tokens": "^4.3.0",
53
53
  "@babel/runtime": "^7.0.0",