@atlaskit/editor-plugin-type-ahead 10.2.2 → 10.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/cjs/pm-plugins/commands/update-list-items.js +3 -1
  3. package/dist/cjs/pm-plugins/main.js +1 -0
  4. package/dist/cjs/pm-plugins/reducer.js +7 -1
  5. package/dist/cjs/typeAheadPlugin.js +3 -1
  6. package/dist/cjs/ui/ContentComponent.js +10 -7
  7. package/dist/cjs/ui/TypeAheadList.js +126 -52
  8. package/dist/cjs/ui/TypeAheadMenu.js +3 -0
  9. package/dist/cjs/ui/TypeAheadPopup.js +3 -0
  10. package/dist/cjs/ui/WrapperTypeAhead.js +3 -1
  11. package/dist/cjs/ui/hooks/build-sectioned-result.js +131 -0
  12. package/dist/cjs/ui/hooks/use-load-items.js +12 -4
  13. package/dist/es2019/pm-plugins/commands/update-list-items.js +3 -2
  14. package/dist/es2019/pm-plugins/main.js +1 -0
  15. package/dist/es2019/pm-plugins/reducer.js +6 -1
  16. package/dist/es2019/typeAheadPlugin.js +3 -1
  17. package/dist/es2019/ui/ContentComponent.js +10 -7
  18. package/dist/es2019/ui/TypeAheadList.js +85 -23
  19. package/dist/es2019/ui/TypeAheadMenu.js +2 -0
  20. package/dist/es2019/ui/TypeAheadPopup.js +2 -0
  21. package/dist/es2019/ui/WrapperTypeAhead.js +3 -1
  22. package/dist/es2019/ui/hooks/build-sectioned-result.js +83 -0
  23. package/dist/es2019/ui/hooks/use-load-items.js +13 -4
  24. package/dist/esm/pm-plugins/commands/update-list-items.js +3 -1
  25. package/dist/esm/pm-plugins/main.js +1 -0
  26. package/dist/esm/pm-plugins/reducer.js +7 -1
  27. package/dist/esm/typeAheadPlugin.js +3 -1
  28. package/dist/esm/ui/ContentComponent.js +10 -7
  29. package/dist/esm/ui/TypeAheadList.js +126 -52
  30. package/dist/esm/ui/TypeAheadMenu.js +3 -0
  31. package/dist/esm/ui/TypeAheadPopup.js +3 -0
  32. package/dist/esm/ui/WrapperTypeAhead.js +3 -1
  33. package/dist/esm/ui/hooks/build-sectioned-result.js +124 -0
  34. package/dist/esm/ui/hooks/use-load-items.js +12 -4
  35. package/dist/types/pm-plugins/commands/update-list-items.d.ts +2 -1
  36. package/dist/types/types/index.d.ts +8 -0
  37. package/dist/types/ui/TypeAheadList.d.ts +3 -1
  38. package/dist/types/ui/TypeAheadPopup.d.ts +3 -2
  39. package/dist/types/ui/hooks/build-sectioned-result.d.ts +11 -0
  40. package/dist/types/ui/hooks/use-load-items.d.ts +2 -1
  41. package/dist/types-ts4.5/pm-plugins/commands/update-list-items.d.ts +2 -1
  42. package/dist/types-ts4.5/types/index.d.ts +8 -0
  43. package/dist/types-ts4.5/ui/TypeAheadList.d.ts +3 -1
  44. package/dist/types-ts4.5/ui/TypeAheadPopup.d.ts +3 -2
  45. package/dist/types-ts4.5/ui/hooks/build-sectioned-result.d.ts +11 -0
  46. package/dist/types-ts4.5/ui/hooks/use-load-items.d.ts +2 -1
  47. package/package.json +4 -4
@@ -12,8 +12,9 @@ var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
12
12
  var _clearListError = require("../../pm-plugins/commands/clear-list-error");
13
13
  var _updateListError = require("../../pm-plugins/commands/update-list-error");
14
14
  var _updateListItems = require("../../pm-plugins/commands/update-list-items");
15
+ var _buildSectionedResult2 = require("./build-sectioned-result");
15
16
  var EMPTY_LIST_ITEM = [];
16
- var useLoadItems = exports.useLoadItems = function useLoadItems(triggerHandler, editorView, query, showViewMore, api) {
17
+ var useLoadItems = exports.useLoadItems = function useLoadItems(triggerHandler, editorView, query, showViewMore, api, intl) {
17
18
  var _useState = (0, _react.useState)(EMPTY_LIST_ITEM),
18
19
  _useState2 = (0, _slicedToArray2.default)(_useState, 2),
19
20
  items = _useState2[0],
@@ -44,7 +45,14 @@ var useLoadItems = exports.useLoadItems = function useLoadItems(triggerHandler,
44
45
  var emptyItem = result.length === 0 && (0, _expValEquals.expValEquals)('platform_editor_insert_menu_ai', 'isEnabled', true) ? (_triggerHandler$getEm = triggerHandler.getEmptyItem) === null || _triggerHandler$getEm === void 0 ? void 0 : _triggerHandler$getEm.call(triggerHandler, {
45
46
  editorState: editorView.state
46
47
  }) : undefined;
47
- var list = result.length > 0 ? result : emptyItem ? [emptyItem] : EMPTY_LIST_ITEM;
48
+ var rawList = result.length > 0 ? result : emptyItem ? [emptyItem] : EMPTY_LIST_ITEM;
49
+ var _buildSectionedResult = (0, _buildSectionedResult2.buildSectionedResult)({
50
+ items: rawList,
51
+ triggerHandler: triggerHandler,
52
+ intl: intl !== null && intl !== void 0 ? intl : null
53
+ }),
54
+ list = _buildSectionedResult.items,
55
+ sections = _buildSectionedResult.sections;
48
56
  if (componentIsMounted.current) {
49
57
  setItems(list);
50
58
  }
@@ -54,7 +62,7 @@ var useLoadItems = exports.useLoadItems = function useLoadItems(triggerHandler,
54
62
  title: 'View more'
55
63
  };
56
64
  queueMicrotask(function () {
57
- (0, _updateListItems.updateListItem)(showViewMore ? list.concat(viewMoreItem) : list)(view.state, view.dispatch);
65
+ (0, _updateListItems.updateListItem)(showViewMore ? list.concat(viewMoreItem) : list, sections)(view.state, view.dispatch);
58
66
  });
59
67
  }).catch(function (e) {
60
68
  if ((0, _experiments.editorExperiment)('platform_editor_offline_editing_web', true)) {
@@ -72,7 +80,7 @@ var useLoadItems = exports.useLoadItems = function useLoadItems(triggerHandler,
72
80
  // ignore because EditorView is mutable but we don't want to
73
81
  // call loadItems when it changes, only when the query changes
74
82
  // eslint-disable-next-line react-hooks/exhaustive-deps
75
- }, [triggerHandler, query]);
83
+ }, [triggerHandler, query, intl]);
76
84
  (0, _react.useEffect)(function () {
77
85
  return function () {
78
86
  componentIsMounted.current = false;
@@ -1,12 +1,13 @@
1
1
  import { ACTIONS } from '../actions';
2
2
  import { pluginKey as typeAheadPluginKey } from '../key';
3
- export const updateListItem = items => {
3
+ export const updateListItem = (items, sections = []) => {
4
4
  return (state, dispatch) => {
5
5
  const tr = state.tr;
6
6
  tr.setMeta(typeAheadPluginKey, {
7
7
  action: ACTIONS.UPDATE_LIST_ITEMS,
8
8
  params: {
9
- items
9
+ items,
10
+ sections
10
11
  }
11
12
  });
12
13
  if (dispatch) {
@@ -53,6 +53,7 @@ export function createPlugin({
53
53
  decorationSet: DecorationSet.empty,
54
54
  decorationElement: null,
55
55
  items: [],
56
+ sections: [],
56
57
  errorInfo: null,
57
58
  selectedIndex: -1,
58
59
  stats: null,
@@ -66,6 +66,7 @@ export const createReducer = ({
66
66
  inputMethod,
67
67
  selectedIndex: typeof selectedIndex === 'number' ? selectedIndex : -1,
68
68
  items: [],
69
+ sections: [],
69
70
  query: reopenQuery || '',
70
71
  removePrefixTriggerOnCancel
71
72
  };
@@ -85,6 +86,7 @@ export const createReducer = ({
85
86
  stats: null,
86
87
  triggerHandler: undefined,
87
88
  items: [],
89
+ sections: [],
88
90
  removePrefixTriggerOnCancel: undefined
89
91
  };
90
92
  };
@@ -140,11 +142,13 @@ export const createReducer = ({
140
142
  ...currentPluginState,
141
143
  errorInfo,
142
144
  items: [],
145
+ sections: [],
143
146
  selectedIndex: -1
144
147
  };
145
148
  } else if (shouldUpdateListItems) {
146
149
  const {
147
- items
150
+ items,
151
+ sections = []
148
152
  } = params;
149
153
  const {
150
154
  selectedIndex
@@ -152,6 +156,7 @@ export const createReducer = ({
152
156
  return {
153
157
  ...currentPluginState,
154
158
  items,
159
+ sections,
155
160
  selectedIndex: Math.max(selectedIndex >= items.length ? items.length - 1 : selectedIndex, -1)
156
161
  };
157
162
  } else if (shouldUpdateSelectedIndex) {
@@ -208,7 +208,7 @@ export const typeAheadPlugin = ({
208
208
  }];
209
209
  },
210
210
  getSharedState(editorState) {
211
- var _state$decorationSet, _state$decorationElem, _state$items, _state$errorInfo, _state$selectedIndex;
211
+ var _state$decorationSet, _state$decorationElem, _state$items, _state$sections, _state$errorInfo, _state$selectedIndex;
212
212
  if (!editorState) {
213
213
  return {
214
214
  query: '',
@@ -219,6 +219,7 @@ export const typeAheadPlugin = ({
219
219
  decorationElement: null,
220
220
  triggerHandler: undefined,
221
221
  items: [],
222
+ sections: [],
222
223
  errorInfo: null,
223
224
  selectedIndex: 0
224
225
  };
@@ -234,6 +235,7 @@ export const typeAheadPlugin = ({
234
235
  decorationElement: (_state$decorationElem = state === null || state === void 0 ? void 0 : state.decorationElement) !== null && _state$decorationElem !== void 0 ? _state$decorationElem : null,
235
236
  triggerHandler: state === null || state === void 0 ? void 0 : state.triggerHandler,
236
237
  items: (_state$items = state === null || state === void 0 ? void 0 : state.items) !== null && _state$items !== void 0 ? _state$items : [],
238
+ sections: (_state$sections = state === null || state === void 0 ? void 0 : state.sections) !== null && _state$sections !== void 0 ? _state$sections : [],
237
239
  errorInfo: (_state$errorInfo = state === null || state === void 0 ? void 0 : state.errorInfo) !== null && _state$errorInfo !== void 0 ? _state$errorInfo : null,
238
240
  selectedIndex: (_state$selectedIndex = state === null || state === void 0 ? void 0 : state.selectedIndex) !== null && _state$selectedIndex !== void 0 ? _state$selectedIndex : 0
239
241
  };
@@ -9,24 +9,26 @@ export function ContentComponent({
9
9
  const {
10
10
  triggerHandler,
11
11
  items,
12
+ sections,
12
13
  errorInfo,
13
14
  decorationElement,
14
15
  decorationSet,
15
16
  query,
16
17
  selectedIndex
17
18
  } = useSharedPluginStateWithSelector(api, ['typeAhead'], states => {
18
- var _states$typeAheadStat, _states$typeAheadStat2, _states$typeAheadStat3, _states$typeAheadStat4, _states$typeAheadStat5, _states$typeAheadStat6, _states$typeAheadStat7;
19
+ var _states$typeAheadStat, _states$typeAheadStat2, _states$typeAheadStat3, _states$typeAheadStat4, _states$typeAheadStat5, _states$typeAheadStat6, _states$typeAheadStat7, _states$typeAheadStat8;
19
20
  return {
20
21
  triggerHandler: (_states$typeAheadStat = states.typeAheadState) === null || _states$typeAheadStat === void 0 ? void 0 : _states$typeAheadStat.triggerHandler,
21
22
  items: (_states$typeAheadStat2 = states.typeAheadState) === null || _states$typeAheadStat2 === void 0 ? void 0 : _states$typeAheadStat2.items,
22
- errorInfo: (_states$typeAheadStat3 = states.typeAheadState) === null || _states$typeAheadStat3 === void 0 ? void 0 : _states$typeAheadStat3.errorInfo,
23
- decorationElement: (_states$typeAheadStat4 = states.typeAheadState) === null || _states$typeAheadStat4 === void 0 ? void 0 : _states$typeAheadStat4.decorationElement,
24
- decorationSet: (_states$typeAheadStat5 = states.typeAheadState) === null || _states$typeAheadStat5 === void 0 ? void 0 : _states$typeAheadStat5.decorationSet,
25
- query: (_states$typeAheadStat6 = states.typeAheadState) === null || _states$typeAheadStat6 === void 0 ? void 0 : _states$typeAheadStat6.query,
26
- selectedIndex: (_states$typeAheadStat7 = states.typeAheadState) === null || _states$typeAheadStat7 === void 0 ? void 0 : _states$typeAheadStat7.selectedIndex
23
+ sections: (_states$typeAheadStat3 = states.typeAheadState) === null || _states$typeAheadStat3 === void 0 ? void 0 : _states$typeAheadStat3.sections,
24
+ errorInfo: (_states$typeAheadStat4 = states.typeAheadState) === null || _states$typeAheadStat4 === void 0 ? void 0 : _states$typeAheadStat4.errorInfo,
25
+ decorationElement: (_states$typeAheadStat5 = states.typeAheadState) === null || _states$typeAheadStat5 === void 0 ? void 0 : _states$typeAheadStat5.decorationElement,
26
+ decorationSet: (_states$typeAheadStat6 = states.typeAheadState) === null || _states$typeAheadStat6 === void 0 ? void 0 : _states$typeAheadStat6.decorationSet,
27
+ query: (_states$typeAheadStat7 = states.typeAheadState) === null || _states$typeAheadStat7 === void 0 ? void 0 : _states$typeAheadStat7.query,
28
+ selectedIndex: (_states$typeAheadStat8 = states.typeAheadState) === null || _states$typeAheadStat8 === void 0 ? void 0 : _states$typeAheadStat8.selectedIndex
27
29
  };
28
30
  });
29
- if (items === undefined || decorationSet === undefined || errorInfo === undefined || decorationElement === undefined || query === undefined || selectedIndex === undefined) {
31
+ if (items === undefined || sections === undefined || decorationSet === undefined || errorInfo === undefined || decorationElement === undefined || query === undefined || selectedIndex === undefined) {
30
32
  return null;
31
33
  }
32
34
  return /*#__PURE__*/React.createElement(TypeAheadMenu, {
@@ -37,6 +39,7 @@ export function ContentComponent({
37
39
  typeAheadState: {
38
40
  triggerHandler,
39
41
  items,
42
+ sections,
40
43
  errorInfo,
41
44
  decorationElement,
42
45
  decorationSet,
@@ -17,6 +17,7 @@ import { AssistiveText } from '@atlaskit/editor-common/ui';
17
17
  import { MenuGroup } from '@atlaskit/menu';
18
18
  import { Text, Box } from '@atlaskit/primitives/compiled';
19
19
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
20
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
20
21
  import { closeTypeAhead } from '../pm-plugins/commands/close-type-ahead';
21
22
  import { updateSelectedIndex } from '../pm-plugins/commands/update-selected-index';
22
23
  import { TYPE_AHEAD_DECORATION_ELEMENT_ID } from '../pm-plugins/constants';
@@ -32,6 +33,36 @@ const list = css({
32
33
  padding: `${"var(--ds-space-100, 8px)"} ${"var(--ds-space-150, 12px)"}`
33
34
  }
34
35
  });
36
+ const buildTypeAheadRows = ({
37
+ itemsLength,
38
+ sections
39
+ }) => {
40
+ if (sections.length === 0) {
41
+ return Array.from({
42
+ length: itemsLength
43
+ }, (_, itemIndex) => ({
44
+ type: 'item',
45
+ itemIndex
46
+ }));
47
+ }
48
+ const sortedSections = [...sections].sort((left, right) => left.startIndex - right.startIndex);
49
+ const sectionsByStartIndex = new Map(sortedSections.map(section => [section.startIndex, section]));
50
+ const rows = [];
51
+ for (let itemIndex = 0; itemIndex < itemsLength; itemIndex++) {
52
+ const section = sectionsByStartIndex.get(itemIndex);
53
+ if (section) {
54
+ rows.push({
55
+ type: 'section',
56
+ section
57
+ });
58
+ }
59
+ rows.push({
60
+ type: 'item',
61
+ itemIndex
62
+ });
63
+ }
64
+ return rows;
65
+ };
35
66
  const TypeaheadAssistiveTextPureComponent = /*#__PURE__*/React.memo(({
36
67
  numberOfResults
37
68
  }) => {
@@ -48,6 +79,7 @@ const TypeaheadAssistiveTextPureComponent = /*#__PURE__*/React.memo(({
48
79
  });
49
80
  const TypeAheadListComponent = /*#__PURE__*/React.memo(({
50
81
  items,
82
+ sections,
51
83
  emptyItem,
52
84
  selectedIndex,
53
85
  editorView,
@@ -61,7 +93,7 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
61
93
  showMoreOptionsButton,
62
94
  onMoreOptionsClicked
63
95
  }) => {
64
- var _triggerHandler$getMo, _decorationElement$qu2;
96
+ var _itemRowIndexByItemIn, _triggerHandler$getMo, _decorationElement$qu2;
65
97
  const listRef = useRef();
66
98
  const listContainerRef = useRef(null);
67
99
  const lastInputMethodRef = useRef('keyboard');
@@ -82,6 +114,21 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
82
114
  fixedWidth: true,
83
115
  defaultHeight: LIST_ITEM_ESTIMATED_HEIGHT
84
116
  }));
117
+ const listRows = useMemo(() => buildTypeAheadRows({
118
+ itemsLength,
119
+ sections: sections || []
120
+ }), [itemsLength, sections]);
121
+ const itemRowIndexByItemIndex = useMemo(() => {
122
+ const rowIndexByItemIndex = new Map();
123
+ listRows.forEach((row, rowIndex) => {
124
+ if (row.type === 'item') {
125
+ rowIndexByItemIndex.set(row.itemIndex, rowIndex);
126
+ }
127
+ });
128
+ return rowIndexByItemIndex;
129
+ }, [listRows]);
130
+ const populatedSectionCount = useMemo(() => listRows.filter(row => row.type === 'section').length, [listRows]);
131
+ const selectedItemRowIndex = selectedIndex >= 0 ? (_itemRowIndexByItemIn = itemRowIndexByItemIndex.get(selectedIndex)) !== null && _itemRowIndexByItemIn !== void 0 ? _itemRowIndexByItemIn : -1 : -1;
85
132
  const onItemsRendered = useCallback(props => {
86
133
  lastVisibleIndexes.current = props;
87
134
  }, []);
@@ -132,28 +179,32 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
132
179
  requestAnimationFrame(() => {
133
180
  requestAnimationFrame(() => {
134
181
  const isViewMoreSelected = showMoreOptionsButton && selectedIndex === itemsLength;
135
- const isSelectedItemVisible = selectedIndex >= lastVisibleStartIndex && selectedIndex <= lastVisibleStopIndex ||
182
+ const isSelectedItemVisible = selectedItemRowIndex >= lastVisibleStartIndex && selectedItemRowIndex <= lastVisibleStopIndex ||
136
183
  // view more is always visible, hence no scrolling
137
184
  isViewMoreSelected;
138
185
 
139
186
  //Should scroll to the list item only when the selectedIndex >= 0 and item is not visible
140
187
  if (!isSelectedItemVisible && selectedIndex !== -1) {
141
- listRef.current.scrollToRow(selectedIndex);
188
+ listRef.current.scrollToRow(selectedItemRowIndex);
142
189
  } else if (selectedIndex === -1) {
143
190
  listRef.current.scrollToRow(0);
144
191
  }
145
192
  });
146
193
  });
147
- }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex, itemsLength, showMoreOptionsButton]);
148
- const onMouseMove = (event, index) => {
194
+ }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex, itemsLength, showMoreOptionsButton, selectedItemRowIndex]);
195
+ const onMouseMove = (event, row) => {
196
+ if (row.type !== 'item') {
197
+ return;
198
+ }
199
+ const itemIndex = row.itemIndex;
149
200
  event.preventDefault();
150
201
  event.stopPropagation();
151
- if (selectedIndex === index) {
202
+ if (selectedIndex === itemIndex) {
152
203
  return;
153
204
  }
154
205
  mouseMovedRef.current = true;
155
206
  lastInputMethodRef.current = 'mouse';
156
- updateSelectedIndex(index, api)(editorView.state, editorView.dispatch);
207
+ updateSelectedIndex(itemIndex, api)(editorView.state, editorView.dispatch);
157
208
  };
158
209
  useLayoutEffect(() => {
159
210
  if (mouseMovedRef.current) {
@@ -167,17 +218,17 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
167
218
  return;
168
219
  }
169
220
  const isViewMoreSelected = showMoreOptionsButton && selectedIndex === itemsLength;
170
- const isSelectedItemVisible = selectedIndex >= lastVisibleStartIndex && selectedIndex <= lastVisibleStopIndex ||
221
+ const isSelectedItemVisible = selectedItemRowIndex >= lastVisibleStartIndex && selectedItemRowIndex <= lastVisibleStopIndex ||
171
222
  // view more is always visible, hence no scrolling
172
223
  isViewMoreSelected;
173
224
 
174
225
  //Should scroll to the list item only when the selectedIndex >= 0 and item is not visible
175
226
  if (!isSelectedItemVisible && selectedIndex !== -1) {
176
- listRef.current.scrollToRow(selectedIndex);
227
+ listRef.current.scrollToRow(selectedItemRowIndex);
177
228
  } else if (selectedIndex === -1) {
178
229
  listRef.current.scrollToRow(0);
179
230
  }
180
- }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex, itemsLength, showMoreOptionsButton]);
231
+ }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex, itemsLength, showMoreOptionsButton, selectedItemRowIndex]);
181
232
  useLayoutEffect(() => {
182
233
  setCache(new CellMeasurerCache({
183
234
  fixedWidth: true,
@@ -193,10 +244,10 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
193
244
  listContainerRef.current.firstChild.scrollTo(0, 0);
194
245
  }
195
246
  });
196
- }, [items]);
247
+ }, [items, sections]);
197
248
  useLayoutEffect(() => {
198
249
  // Exclude view more item from the count
199
- const itemsToRender = showMoreOptionsButton ? items.slice(0, -1) : items;
250
+ const itemsToRender = editorExperiment('platform_editor_agent_mentions', true) ? listRows : showMoreOptionsButton ? items.slice(0, -1) : items;
200
251
  const height = Math.min(
201
252
  // eslint-disable-next-line @atlassian/perf-linting/no-expensive-computations-in-render -- Ignored via go/ees017 (to be fixed)
202
253
  itemsToRender.reduce((prevValue, currentValue, index) => {
@@ -205,7 +256,7 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
205
256
  });
206
257
  }, 0), fitHeight);
207
258
  setHeight(height);
208
- }, [items, cache, fitHeight, showMoreOptionsButton]);
259
+ }, [listRows, items, cache, fitHeight, showMoreOptionsButton]);
209
260
  useLayoutEffect(() => {
210
261
  if (!listContainerRef.current) {
211
262
  return;
@@ -286,7 +337,10 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
286
337
  isScrolling,
287
338
  isVisible
288
339
  }) => {
289
- const currentItem = items[index];
340
+ const currentRow = listRows[index];
341
+ if (!currentRow) {
342
+ return null;
343
+ }
290
344
  return jsx(CellMeasurer, {
291
345
  key: key,
292
346
  cache: cache,
@@ -305,20 +359,28 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
305
359
  isScrolling: isScrolling
306
360
  // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
307
361
  ,
308
- onMouseMove: e => onMouseMove(e, index)
309
- }, jsx(TypeAheadListItem, {
310
- key: items[index].title,
311
- item: currentItem,
362
+ onMouseMove: e => onMouseMove(e, currentRow)
363
+ }, currentRow.type === 'section' ? populatedSectionCount === 1 ? null : jsx(Box, {
364
+ paddingInline: "space.150",
365
+ paddingBlock: "space.050"
366
+ }, jsx(Text, {
367
+ as: "span",
368
+ size: "small",
369
+ color: "color.text.subtle",
370
+ weight: "medium"
371
+ }, currentRow.section.title)) : jsx(TypeAheadListItem, {
372
+ key: items[currentRow.itemIndex].title,
373
+ item: items[currentRow.itemIndex],
312
374
  firstOnlineSupportedIndex: firstOnlineSupportedRow,
313
375
  itemsLength: itemsLength,
314
- itemIndex: index,
376
+ itemIndex: currentRow.itemIndex,
315
377
  selectedIndex: selectedIndex
316
378
  // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
317
379
  ,
318
- onItemClick: (mode, index) => {
319
- actions.onItemClick(mode, index, INPUT_METHOD.MOUSE);
380
+ onItemClick: (mode, itemIndex) => {
381
+ actions.onItemClick(mode, itemIndex, INPUT_METHOD.MOUSE);
320
382
  },
321
- ariaLabel: getTypeAheadListAriaLabels(triggerHandler === null || triggerHandler === void 0 ? void 0 : triggerHandler.trigger, intl, currentItem).listItemAriaLabel,
383
+ ariaLabel: getTypeAheadListAriaLabels(triggerHandler === null || triggerHandler === void 0 ? void 0 : triggerHandler.trigger, intl, items[currentRow.itemIndex]).listItemAriaLabel,
322
384
  moreElementsInQuickInsertViewEnabled: moreElementsInQuickInsertViewEnabled,
323
385
  api: api,
324
386
  lastInputMethodRef: lastInputMethodRef
@@ -351,7 +413,7 @@ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
351
413
  ref: listRef
352
414
  // Skip rendering the view more button in the list
353
415
  ,
354
- rowCount: itemsLength,
416
+ rowCount: listRows.length,
355
417
  rowHeight: cache.rowHeight,
356
418
  onRowsRendered: onItemsRendered,
357
419
  width: LIST_WIDTH,
@@ -17,6 +17,7 @@ export const TypeAheadMenu = /*#__PURE__*/React.memo(({
17
17
  const {
18
18
  triggerHandler,
19
19
  items,
20
+ sections = [],
20
21
  errorInfo,
21
22
  decorationElement,
22
23
  decorationSet,
@@ -94,6 +95,7 @@ export const TypeAheadMenu = /*#__PURE__*/React.memo(({
94
95
  anchorElement: decorationElement,
95
96
  triggerHandler: triggerHandler,
96
97
  items: items,
98
+ sections: sections,
97
99
  emptyItem: emptyItem,
98
100
  errorInfo: errorInfo,
99
101
  selectedIndex: selectedIndex,
@@ -61,6 +61,7 @@ export const TypeAheadPopup = /*#__PURE__*/React.memo(props => {
61
61
  popupsBoundariesElement,
62
62
  popupsScrollableElement,
63
63
  items,
64
+ sections = [],
64
65
  emptyItem,
65
66
  errorInfo,
66
67
  selectedIndex,
@@ -341,6 +342,7 @@ export const TypeAheadPopup = /*#__PURE__*/React.memo(props => {
341
342
  triggerHandler: triggerHandler
342
343
  }), jsx(TypeAheadList, {
343
344
  items: items,
345
+ sections: sections,
344
346
  emptyItem: emptyItem,
345
347
  selectedIndex: selectedIndex
346
348
  // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
@@ -1,4 +1,5 @@
1
1
  import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
+ import { useIntl } from 'react-intl';
2
3
  import { ACTION, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
3
4
  import { SelectItemMode } from '@atlaskit/editor-common/type-ahead';
4
5
  import { fg } from '@atlaskit/platform-feature-flags';
@@ -31,11 +32,12 @@ export const WrapperTypeAhead = /*#__PURE__*/React.memo(({
31
32
  if (editorExperiment('platform_editor_controls', 'variant1')) {
32
33
  showMoreOptionsButton = !!(triggerHandler !== null && triggerHandler !== void 0 && triggerHandler.getMoreOptionsButtonConfig);
33
34
  }
35
+ const intl = useIntl();
34
36
  const [closed, setClosed] = useState(false);
35
37
  const [query, setQuery] = useState(reopenQuery || '');
36
38
  const queryRef = useRef(query);
37
39
  const editorViewRef = useRef(editorView);
38
- const items = useLoadItems(triggerHandler, editorView, query, showMoreOptionsButton, api);
40
+ const items = useLoadItems(triggerHandler, editorView, query, showMoreOptionsButton, api, intl);
39
41
  useEffect(() => {
40
42
  if (!closed && fg('platform_editor_ease_of_use_metrics')) {
41
43
  var _api$metrics;
@@ -0,0 +1,83 @@
1
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
2
+ export const buildSectionedResult = ({
3
+ items,
4
+ triggerHandler,
5
+ intl
6
+ }) => {
7
+ var _triggerHandler$getSe;
8
+ if (!editorExperiment('platform_editor_agent_mentions', true) || !intl) {
9
+ return {
10
+ items,
11
+ sections: []
12
+ };
13
+ }
14
+ const sectionDefinitions = (_triggerHandler$getSe = triggerHandler.getSections) === null || _triggerHandler$getSe === void 0 ? void 0 : _triggerHandler$getSe.call(triggerHandler, {
15
+ intl
16
+ });
17
+ if (!sectionDefinitions || sectionDefinitions.length === 0) {
18
+ return {
19
+ items,
20
+ sections: []
21
+ };
22
+ }
23
+
24
+ // Track which item indexes have been claimed by a section, or excluded (matched a section but
25
+ // cut by that section's limit). Items excluded by limit should not appear anywhere in the output.
26
+ const assignedToSection = new Set();
27
+ const excludedByLimit = new Set();
28
+ const groupedBySection = new Map();
29
+ for (const section of sectionDefinitions) {
30
+ const matchingIndexes = [];
31
+ for (let i = 0; i < items.length; i++) {
32
+ if (assignedToSection.has(i) || excludedByLimit.has(i)) {
33
+ continue;
34
+ }
35
+ if (section.filter(items[i])) {
36
+ matchingIndexes.push(i);
37
+ }
38
+ }
39
+ const acceptedIndexes = section.limit !== undefined ? matchingIndexes.slice(0, section.limit) : matchingIndexes;
40
+ const rejectedByLimit = matchingIndexes.slice(acceptedIndexes.length);
41
+ for (const i of acceptedIndexes) {
42
+ assignedToSection.add(i);
43
+ }
44
+ for (const i of rejectedByLimit) {
45
+ excludedByLimit.add(i);
46
+ }
47
+ if (acceptedIndexes.length === 0) {
48
+ continue;
49
+ }
50
+ groupedBySection.set(section.id, {
51
+ indexes: acceptedIndexes,
52
+ section
53
+ });
54
+ }
55
+ const flattenedItems = [];
56
+ const sections = [];
57
+ for (const section of sectionDefinitions) {
58
+ const grouped = groupedBySection.get(section.id);
59
+ if (!grouped) {
60
+ continue;
61
+ }
62
+ const startIndex = flattenedItems.length;
63
+ flattenedItems.push(...grouped.indexes.map(i => items[i]));
64
+ const endIndex = flattenedItems.length - 1;
65
+ sections.push({
66
+ endIndex,
67
+ id: section.id,
68
+ startIndex,
69
+ title: section.title
70
+ });
71
+ }
72
+
73
+ // Append items not claimed by any section and not excluded by a limit
74
+ for (let i = 0; i < items.length; i++) {
75
+ if (!assignedToSection.has(i) && !excludedByLimit.has(i)) {
76
+ flattenedItems.push(items[i]);
77
+ }
78
+ }
79
+ return {
80
+ items: flattenedItems,
81
+ sections
82
+ };
83
+ };
@@ -4,8 +4,9 @@ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
4
4
  import { clearListError } from '../../pm-plugins/commands/clear-list-error';
5
5
  import { updateListError } from '../../pm-plugins/commands/update-list-error';
6
6
  import { updateListItem } from '../../pm-plugins/commands/update-list-items';
7
+ import { buildSectionedResult } from './build-sectioned-result';
7
8
  const EMPTY_LIST_ITEM = [];
8
- export const useLoadItems = (triggerHandler, editorView, query, showViewMore, api) => {
9
+ export const useLoadItems = (triggerHandler, editorView, query, showViewMore, api, intl) => {
9
10
  const [items, setItems] = useState(EMPTY_LIST_ITEM);
10
11
  const componentIsMounted = useRef(true);
11
12
  const editorViewRef = useRef(editorView);
@@ -35,7 +36,15 @@ export const useLoadItems = (triggerHandler, editorView, query, showViewMore, ap
35
36
  const emptyItem = result.length === 0 && expValEquals('platform_editor_insert_menu_ai', 'isEnabled', true) ? (_triggerHandler$getEm = triggerHandler.getEmptyItem) === null || _triggerHandler$getEm === void 0 ? void 0 : _triggerHandler$getEm.call(triggerHandler, {
36
37
  editorState: editorView.state
37
38
  }) : undefined;
38
- const list = result.length > 0 ? result : emptyItem ? [emptyItem] : EMPTY_LIST_ITEM;
39
+ const rawList = result.length > 0 ? result : emptyItem ? [emptyItem] : EMPTY_LIST_ITEM;
40
+ const {
41
+ items: list,
42
+ sections
43
+ } = buildSectionedResult({
44
+ items: rawList,
45
+ triggerHandler,
46
+ intl: intl !== null && intl !== void 0 ? intl : null
47
+ });
39
48
  if (componentIsMounted.current) {
40
49
  setItems(list);
41
50
  }
@@ -45,7 +54,7 @@ export const useLoadItems = (triggerHandler, editorView, query, showViewMore, ap
45
54
  title: 'View more'
46
55
  };
47
56
  queueMicrotask(() => {
48
- updateListItem(showViewMore ? list.concat(viewMoreItem) : list)(view.state, view.dispatch);
57
+ updateListItem(showViewMore ? list.concat(viewMoreItem) : list, sections)(view.state, view.dispatch);
49
58
  });
50
59
  }).catch(e => {
51
60
  if (editorExperiment('platform_editor_offline_editing_web', true)) {
@@ -63,7 +72,7 @@ export const useLoadItems = (triggerHandler, editorView, query, showViewMore, ap
63
72
  // ignore because EditorView is mutable but we don't want to
64
73
  // call loadItems when it changes, only when the query changes
65
74
  // eslint-disable-next-line react-hooks/exhaustive-deps
66
- }, [triggerHandler, query]);
75
+ }, [triggerHandler, query, intl]);
67
76
  useEffect(() => {
68
77
  return () => {
69
78
  componentIsMounted.current = false;
@@ -1,12 +1,14 @@
1
1
  import { ACTIONS } from '../actions';
2
2
  import { pluginKey as typeAheadPluginKey } from '../key';
3
3
  export var updateListItem = function updateListItem(items) {
4
+ var sections = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
4
5
  return function (state, dispatch) {
5
6
  var tr = state.tr;
6
7
  tr.setMeta(typeAheadPluginKey, {
7
8
  action: ACTIONS.UPDATE_LIST_ITEMS,
8
9
  params: {
9
- items: items
10
+ items: items,
11
+ sections: sections
10
12
  }
11
13
  });
12
14
  if (dispatch) {
@@ -53,6 +53,7 @@ export function createPlugin(_ref) {
53
53
  decorationSet: DecorationSet.empty,
54
54
  decorationElement: null,
55
55
  items: [],
56
+ sections: [],
56
57
  errorInfo: null,
57
58
  selectedIndex: -1,
58
59
  stats: null,
@@ -69,6 +69,7 @@ export var createReducer = function createReducer(_ref) {
69
69
  inputMethod: inputMethod,
70
70
  selectedIndex: typeof selectedIndex === 'number' ? selectedIndex : -1,
71
71
  items: [],
72
+ sections: [],
72
73
  query: reopenQuery || '',
73
74
  removePrefixTriggerOnCancel: removePrefixTriggerOnCancel
74
75
  });
@@ -87,6 +88,7 @@ export var createReducer = function createReducer(_ref) {
87
88
  stats: null,
88
89
  triggerHandler: undefined,
89
90
  items: [],
91
+ sections: [],
90
92
  removePrefixTriggerOnCancel: undefined
91
93
  });
92
94
  };
@@ -137,13 +139,17 @@ export var createReducer = function createReducer(_ref) {
137
139
  return _objectSpread(_objectSpread({}, currentPluginState), {}, {
138
140
  errorInfo: errorInfo,
139
141
  items: [],
142
+ sections: [],
140
143
  selectedIndex: -1
141
144
  });
142
145
  } else if (shouldUpdateListItems) {
143
- var items = params.items;
146
+ var items = params.items,
147
+ _params$sections = params.sections,
148
+ sections = _params$sections === void 0 ? [] : _params$sections;
144
149
  var selectedIndex = currentPluginState.selectedIndex;
145
150
  return _objectSpread(_objectSpread({}, currentPluginState), {}, {
146
151
  items: items,
152
+ sections: sections,
147
153
  selectedIndex: Math.max(selectedIndex >= items.length ? items.length - 1 : selectedIndex, -1)
148
154
  });
149
155
  } else if (shouldUpdateSelectedIndex) {