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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/commands/package.json +17 -0
  3. package/dist/cjs/entry-points/commands.js +18 -0
  4. package/dist/cjs/pm-plugins/commands/open-typeahead-at-cursor.js +7 -2
  5. package/dist/cjs/pm-plugins/commands/update-list-items.js +3 -1
  6. package/dist/cjs/pm-plugins/main.js +54 -0
  7. package/dist/cjs/pm-plugins/reducer.js +7 -1
  8. package/dist/cjs/typeAheadPlugin.js +3 -1
  9. package/dist/cjs/ui/ContentComponent.js +10 -7
  10. package/dist/cjs/ui/TypeAheadList.js +126 -52
  11. package/dist/cjs/ui/TypeAheadMenu.js +3 -0
  12. package/dist/cjs/ui/TypeAheadPopup.js +3 -0
  13. package/dist/cjs/ui/WrapperTypeAhead.js +3 -1
  14. package/dist/cjs/ui/hooks/build-sectioned-result.js +131 -0
  15. package/dist/cjs/ui/hooks/use-load-items.js +12 -4
  16. package/dist/es2019/entry-points/commands.js +2 -0
  17. package/dist/es2019/pm-plugins/commands/open-typeahead-at-cursor.js +7 -2
  18. package/dist/es2019/pm-plugins/commands/update-list-items.js +3 -2
  19. package/dist/es2019/pm-plugins/main.js +54 -0
  20. package/dist/es2019/pm-plugins/reducer.js +6 -1
  21. package/dist/es2019/typeAheadPlugin.js +3 -1
  22. package/dist/es2019/ui/ContentComponent.js +10 -7
  23. package/dist/es2019/ui/TypeAheadList.js +85 -23
  24. package/dist/es2019/ui/TypeAheadMenu.js +2 -0
  25. package/dist/es2019/ui/TypeAheadPopup.js +2 -0
  26. package/dist/es2019/ui/WrapperTypeAhead.js +3 -1
  27. package/dist/es2019/ui/hooks/build-sectioned-result.js +83 -0
  28. package/dist/es2019/ui/hooks/use-load-items.js +13 -4
  29. package/dist/esm/entry-points/commands.js +2 -0
  30. package/dist/esm/pm-plugins/commands/open-typeahead-at-cursor.js +7 -2
  31. package/dist/esm/pm-plugins/commands/update-list-items.js +3 -1
  32. package/dist/esm/pm-plugins/main.js +54 -0
  33. package/dist/esm/pm-plugins/reducer.js +7 -1
  34. package/dist/esm/typeAheadPlugin.js +3 -1
  35. package/dist/esm/ui/ContentComponent.js +10 -7
  36. package/dist/esm/ui/TypeAheadList.js +126 -52
  37. package/dist/esm/ui/TypeAheadMenu.js +3 -0
  38. package/dist/esm/ui/TypeAheadPopup.js +3 -0
  39. package/dist/esm/ui/WrapperTypeAhead.js +3 -1
  40. package/dist/esm/ui/hooks/build-sectioned-result.js +124 -0
  41. package/dist/esm/ui/hooks/use-load-items.js +12 -4
  42. package/dist/types/entry-points/commands.d.ts +1 -0
  43. package/dist/types/pm-plugins/commands/update-list-items.d.ts +2 -1
  44. package/dist/types/types/index.d.ts +8 -0
  45. package/dist/types/ui/TypeAheadList.d.ts +3 -1
  46. package/dist/types/ui/TypeAheadPopup.d.ts +3 -2
  47. package/dist/types/ui/hooks/build-sectioned-result.d.ts +11 -0
  48. package/dist/types/ui/hooks/use-load-items.d.ts +2 -1
  49. package/dist/types-ts4.5/entry-points/commands.d.ts +1 -0
  50. package/dist/types-ts4.5/pm-plugins/commands/update-list-items.d.ts +2 -1
  51. package/dist/types-ts4.5/types/index.d.ts +8 -0
  52. package/dist/types-ts4.5/ui/TypeAheadList.d.ts +3 -1
  53. package/dist/types-ts4.5/ui/TypeAheadPopup.d.ts +3 -2
  54. package/dist/types-ts4.5/ui/hooks/build-sectioned-result.d.ts +11 -0
  55. package/dist/types-ts4.5/ui/hooks/use-load-items.d.ts +2 -1
  56. package/package.json +5 -5
@@ -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;
@@ -0,0 +1,2 @@
1
+ /* eslint-disable @atlaskit/editor/no-re-export */
2
+ export { openTypeAhead, openTypeAheadAtCursor } from '../pm-plugins/commands/open-typeahead-at-cursor';
@@ -1,5 +1,6 @@
1
1
  import { GapCursorSelection } from '@atlaskit/editor-common/selection';
2
2
  import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
3
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
3
4
  import { ACTIONS } from '../actions';
4
5
  import { pluginKey } from '../key';
5
6
  export var openTypeAhead = function openTypeAhead(props) {
@@ -43,7 +44,7 @@ export var openTypeAheadAtCursor = function openTypeAheadAtCursor(_ref) {
43
44
  // delete 1 pos before wherever selection is now - that will delete the empty space
44
45
  tr.delete(tr.selection.from - 1, tr.selection.from);
45
46
  } else {
46
- var _selection$$head, _selection$$head$pare, _selection$$head$pare2;
47
+ var _selection$$head$pare, _selection$$head$pare2, _selection$$head;
47
48
  if (selection instanceof NodeSelection) {
48
49
  if (isInline) {
49
50
  tr.deleteSelection();
@@ -72,7 +73,11 @@ export var openTypeAheadAtCursor = function openTypeAheadAtCursor(_ref) {
72
73
  // being inserted due to composition by checking if we have the trigger
73
74
  // directly before the typeahead. This should not happen unless it has
74
75
  // been eroneously added because we require whitespace/newline for typeahead.
75
- if (cursorPos >= 2 && !!(selection !== null && selection !== void 0 && (_selection$$head = selection.$head) !== null && _selection$$head !== void 0 && (_selection$$head = _selection$$head.parent) !== null && _selection$$head !== void 0 && _selection$$head.textContent) && (_selection$$head$pare = (_selection$$head$pare2 = selection.$head.parent.textContent).endsWith) !== null && _selection$$head$pare !== void 0 && _selection$$head$pare.call(_selection$$head$pare2, triggerHandler.trigger)) {
76
+ // Check if the text ends with the trigger character (or any character matched
77
+ // by customRegex, to support wide-char variants like fullwidth slash /)
78
+ var triggerPattern = expValEquals('platform_editor_wide_slash_trigger', 'isEnabled', true) && triggerHandler.customRegex ? new RegExp("(".concat(triggerHandler.customRegex, ")$"), 'u') : null;
79
+ var endsWithTrigger = ((_selection$$head$pare = (_selection$$head$pare2 = selection.$head.parent.textContent).endsWith) === null || _selection$$head$pare === void 0 ? void 0 : _selection$$head$pare.call(_selection$$head$pare2, triggerHandler.trigger)) || triggerPattern && triggerPattern.test(selection.$head.parent.textContent);
80
+ if (cursorPos >= 2 && !!(selection !== null && selection !== void 0 && (_selection$$head = selection.$head) !== null && _selection$$head !== void 0 && (_selection$$head = _selection$$head.parent) !== null && _selection$$head !== void 0 && _selection$$head.textContent) && endsWithTrigger) {
76
81
  tr.delete(cursorPos - 1, cursorPos);
77
82
  }
78
83
  }
@@ -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) {
@@ -1,9 +1,12 @@
1
1
  import { InsertTypeAheadStep } from '@atlaskit/adf-schema/steps';
2
+ import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
3
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
4
  import { closest } from '@atlaskit/editor-common/utils';
4
5
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
5
6
  import { fg } from '@atlaskit/platform-feature-flags';
7
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
6
8
  import { ACTIONS } from './actions';
9
+ import { openTypeAheadAtCursor } from './commands/open-typeahead-at-cursor';
7
10
  import { TYPE_AHEAD_DECORATION_DATA_ATTRIBUTE } from './constants';
8
11
  import { factoryDecorations } from './decorations';
9
12
  import { isInsertionTransaction } from './isInsertionTransaction';
@@ -43,6 +46,11 @@ export function createPlugin(_ref) {
43
46
  typeAheadHandlers: typeAheadHandlers,
44
47
  popupMountRef: popupMountRef
45
48
  });
49
+
50
+ // Tracks a wide-char trigger handler detected during IME composition (e.g. /).
51
+ // Set by compositionupdate, cleared by compositionend. Used by handleKeyDown
52
+ // to intercept Enter that confirms the composition.
53
+ var pendingWideSlashHandler = null;
46
54
  return new SafePlugin({
47
55
  key: pluginKey,
48
56
  state: {
@@ -53,6 +61,7 @@ export function createPlugin(_ref) {
53
61
  decorationSet: DecorationSet.empty,
54
62
  decorationElement: null,
55
63
  items: [],
64
+ sections: [],
56
65
  errorInfo: null,
57
66
  selectedIndex: -1,
58
67
  stats: null,
@@ -94,8 +103,53 @@ export function createPlugin(_ref) {
94
103
  var _pluginKey$getState;
95
104
  return (_pluginKey$getState = pluginKey.getState(state)) === null || _pluginKey$getState === void 0 ? void 0 : _pluginKey$getState.decorationSet;
96
105
  },
106
+ handleKeyDown: function handleKeyDown(view, event) {
107
+ // When composing a wide-char trigger (e.g. /), intercept the Enter key
108
+ // that confirms the composition so we can open the typeahead instead.
109
+ if (pendingWideSlashHandler && event.isComposing && (event.key === 'Enter' || event.keyCode === 13)) {
110
+ var handler = pendingWideSlashHandler;
111
+ pendingWideSlashHandler = null;
112
+ // Defer until ProseMirror has flushed the composed text into its state.
113
+ setTimeout(function () {
114
+ var command = openTypeAheadAtCursor({
115
+ triggerHandler: handler,
116
+ inputMethod: INPUT_METHOD.KEYBOARD
117
+ });
118
+ var tr = command({
119
+ tr: view.state.tr
120
+ });
121
+ if (tr) {
122
+ view.dispatch(tr);
123
+ }
124
+ }, 0);
125
+ return true;
126
+ }
127
+ return false;
128
+ },
97
129
  handleDOMEvents: {
130
+ compositionupdate: function compositionupdate(view, event) {
131
+ // When the experiment is on, track whether the current composition
132
+ // exactly matches a wide-char trigger (e.g. / from Japanese keyboard).
133
+ // We can't open the typeahead yet because composition is still active,
134
+ // but we record the matching handler so the next keydown (Enter) can use it.
135
+ if (expValEquals('platform_editor_wide_slash_trigger', 'isEnabled', true)) {
136
+ var _event$data, _typeAheadHandlers$fi;
137
+ var pendingData = (_event$data = event.data) !== null && _event$data !== void 0 ? _event$data : '';
138
+ pendingWideSlashHandler = (_typeAheadHandlers$fi = typeAheadHandlers.find(function (handler) {
139
+ if (!handler.customRegex) {
140
+ return false;
141
+ }
142
+ // Only match if the entire composition is a trigger character
143
+ var pattern = new RegExp("^(".concat(handler.customRegex, ")$"), 'u');
144
+ return pattern.test(pendingData);
145
+ })) !== null && _typeAheadHandlers$fi !== void 0 ? _typeAheadHandlers$fi : null;
146
+ }
147
+ return false;
148
+ },
98
149
  compositionend: function compositionend(view, event) {
150
+ // Clear the pending handler when composition ends (cancelled or committed
151
+ // via a non-Enter key like Space, which we don't want to intercept).
152
+ pendingWideSlashHandler = null;
99
153
  return false;
100
154
  },
101
155
  click: function click(view, event) {
@@ -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) {
@@ -206,7 +206,7 @@ export var typeAheadPlugin = function typeAheadPlugin(_ref) {
206
206
  }];
207
207
  },
208
208
  getSharedState: function getSharedState(editorState) {
209
- var _state$decorationSet, _state$decorationElem, _state$items, _state$errorInfo, _state$selectedIndex;
209
+ var _state$decorationSet, _state$decorationElem, _state$items, _state$sections, _state$errorInfo, _state$selectedIndex;
210
210
  if (!editorState) {
211
211
  return {
212
212
  query: '',
@@ -217,6 +217,7 @@ export var typeAheadPlugin = function typeAheadPlugin(_ref) {
217
217
  decorationElement: null,
218
218
  triggerHandler: undefined,
219
219
  items: [],
220
+ sections: [],
220
221
  errorInfo: null,
221
222
  selectedIndex: 0
222
223
  };
@@ -232,6 +233,7 @@ export var typeAheadPlugin = function typeAheadPlugin(_ref) {
232
233
  decorationElement: (_state$decorationElem = state === null || state === void 0 ? void 0 : state.decorationElement) !== null && _state$decorationElem !== void 0 ? _state$decorationElem : null,
233
234
  triggerHandler: state === null || state === void 0 ? void 0 : state.triggerHandler,
234
235
  items: (_state$items = state === null || state === void 0 ? void 0 : state.items) !== null && _state$items !== void 0 ? _state$items : [],
236
+ sections: (_state$sections = state === null || state === void 0 ? void 0 : state.sections) !== null && _state$sections !== void 0 ? _state$sections : [],
235
237
  errorInfo: (_state$errorInfo = state === null || state === void 0 ? void 0 : state.errorInfo) !== null && _state$errorInfo !== void 0 ? _state$errorInfo : null,
236
238
  selectedIndex: (_state$selectedIndex = state === null || state === void 0 ? void 0 : state.selectedIndex) !== null && _state$selectedIndex !== void 0 ? _state$selectedIndex : 0
237
239
  };
@@ -6,25 +6,27 @@ export function ContentComponent(_ref) {
6
6
  editorView = _ref.editorView,
7
7
  popupMountRef = _ref.popupMountRef;
8
8
  var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['typeAhead'], function (states) {
9
- var _states$typeAheadStat, _states$typeAheadStat2, _states$typeAheadStat3, _states$typeAheadStat4, _states$typeAheadStat5, _states$typeAheadStat6, _states$typeAheadStat7;
9
+ var _states$typeAheadStat, _states$typeAheadStat2, _states$typeAheadStat3, _states$typeAheadStat4, _states$typeAheadStat5, _states$typeAheadStat6, _states$typeAheadStat7, _states$typeAheadStat8;
10
10
  return {
11
11
  triggerHandler: (_states$typeAheadStat = states.typeAheadState) === null || _states$typeAheadStat === void 0 ? void 0 : _states$typeAheadStat.triggerHandler,
12
12
  items: (_states$typeAheadStat2 = states.typeAheadState) === null || _states$typeAheadStat2 === void 0 ? void 0 : _states$typeAheadStat2.items,
13
- errorInfo: (_states$typeAheadStat3 = states.typeAheadState) === null || _states$typeAheadStat3 === void 0 ? void 0 : _states$typeAheadStat3.errorInfo,
14
- decorationElement: (_states$typeAheadStat4 = states.typeAheadState) === null || _states$typeAheadStat4 === void 0 ? void 0 : _states$typeAheadStat4.decorationElement,
15
- decorationSet: (_states$typeAheadStat5 = states.typeAheadState) === null || _states$typeAheadStat5 === void 0 ? void 0 : _states$typeAheadStat5.decorationSet,
16
- query: (_states$typeAheadStat6 = states.typeAheadState) === null || _states$typeAheadStat6 === void 0 ? void 0 : _states$typeAheadStat6.query,
17
- selectedIndex: (_states$typeAheadStat7 = states.typeAheadState) === null || _states$typeAheadStat7 === void 0 ? void 0 : _states$typeAheadStat7.selectedIndex
13
+ sections: (_states$typeAheadStat3 = states.typeAheadState) === null || _states$typeAheadStat3 === void 0 ? void 0 : _states$typeAheadStat3.sections,
14
+ errorInfo: (_states$typeAheadStat4 = states.typeAheadState) === null || _states$typeAheadStat4 === void 0 ? void 0 : _states$typeAheadStat4.errorInfo,
15
+ decorationElement: (_states$typeAheadStat5 = states.typeAheadState) === null || _states$typeAheadStat5 === void 0 ? void 0 : _states$typeAheadStat5.decorationElement,
16
+ decorationSet: (_states$typeAheadStat6 = states.typeAheadState) === null || _states$typeAheadStat6 === void 0 ? void 0 : _states$typeAheadStat6.decorationSet,
17
+ query: (_states$typeAheadStat7 = states.typeAheadState) === null || _states$typeAheadStat7 === void 0 ? void 0 : _states$typeAheadStat7.query,
18
+ selectedIndex: (_states$typeAheadStat8 = states.typeAheadState) === null || _states$typeAheadStat8 === void 0 ? void 0 : _states$typeAheadStat8.selectedIndex
18
19
  };
19
20
  }),
20
21
  triggerHandler = _useSharedPluginState.triggerHandler,
21
22
  items = _useSharedPluginState.items,
23
+ sections = _useSharedPluginState.sections,
22
24
  errorInfo = _useSharedPluginState.errorInfo,
23
25
  decorationElement = _useSharedPluginState.decorationElement,
24
26
  decorationSet = _useSharedPluginState.decorationSet,
25
27
  query = _useSharedPluginState.query,
26
28
  selectedIndex = _useSharedPluginState.selectedIndex;
27
- if (items === undefined || decorationSet === undefined || errorInfo === undefined || decorationElement === undefined || query === undefined || selectedIndex === undefined) {
29
+ if (items === undefined || sections === undefined || decorationSet === undefined || errorInfo === undefined || decorationElement === undefined || query === undefined || selectedIndex === undefined) {
28
30
  return null;
29
31
  }
30
32
  return /*#__PURE__*/React.createElement(TypeAheadMenu, {
@@ -35,6 +37,7 @@ export function ContentComponent(_ref) {
35
37
  typeAheadState: {
36
38
  triggerHandler: triggerHandler,
37
39
  items: items,
40
+ sections: sections,
38
41
  errorInfo: errorInfo,
39
42
  decorationElement: decorationElement,
40
43
  decorationSet: decorationSet,