@atlaskit/editor-plugin-type-ahead 0.6.0 → 0.7.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 (160) hide show
  1. package/.eslintrc.js +11 -0
  2. package/CHANGELOG.md +6 -0
  3. package/dist/cjs/api.js +215 -0
  4. package/dist/cjs/commands/insert-type-ahead-item.js +205 -0
  5. package/dist/cjs/commands/update-list-items.js +23 -0
  6. package/dist/cjs/commands/update-query.js +27 -0
  7. package/dist/cjs/commands/update-selected-index.js +27 -0
  8. package/dist/cjs/index.js +8 -1
  9. package/dist/cjs/insert-utils.js +107 -0
  10. package/dist/cjs/messages.js +79 -0
  11. package/dist/cjs/plugin.js +382 -0
  12. package/dist/cjs/pm-plugins/actions.js +16 -0
  13. package/dist/cjs/pm-plugins/decorations.js +148 -0
  14. package/dist/cjs/pm-plugins/input-rules.js +36 -0
  15. package/dist/cjs/pm-plugins/insert-item-plugin.js +22 -0
  16. package/dist/cjs/pm-plugins/key.js +8 -0
  17. package/dist/cjs/pm-plugins/main.js +110 -0
  18. package/dist/cjs/pm-plugins/reducer.js +158 -0
  19. package/dist/cjs/pm-plugins/utils.js +18 -0
  20. package/dist/cjs/stats-modifier.js +42 -0
  21. package/dist/cjs/transforms/close-type-ahead.js +13 -0
  22. package/dist/cjs/transforms/open-typeahead-at-cursor.js +75 -0
  23. package/dist/cjs/transforms/set-selection-before-query.js +18 -0
  24. package/dist/cjs/ui/AssistiveText.js +120 -0
  25. package/dist/cjs/ui/InputQuery.js +400 -0
  26. package/dist/cjs/ui/TypeAheadList.js +285 -0
  27. package/dist/cjs/ui/TypeAheadListItem.js +181 -0
  28. package/dist/cjs/ui/TypeAheadPopup.js +230 -0
  29. package/dist/cjs/ui/WrapperTypeAhead.js +127 -0
  30. package/dist/cjs/ui/hooks/use-item-insert.js +109 -0
  31. package/dist/cjs/ui/hooks/use-load-items.js +50 -0
  32. package/dist/cjs/ui/hooks/use-on-force-select.js +41 -0
  33. package/dist/cjs/utils.js +130 -0
  34. package/dist/es2019/api.js +205 -0
  35. package/dist/es2019/commands/insert-type-ahead-item.js +204 -0
  36. package/dist/es2019/commands/update-list-items.js +17 -0
  37. package/dist/es2019/commands/update-query.js +21 -0
  38. package/dist/es2019/commands/update-selected-index.js +21 -0
  39. package/dist/es2019/index.js +1 -1
  40. package/dist/es2019/insert-utils.js +106 -0
  41. package/dist/es2019/messages.js +73 -0
  42. package/dist/es2019/plugin.js +381 -0
  43. package/dist/es2019/pm-plugins/actions.js +10 -0
  44. package/dist/es2019/pm-plugins/decorations.js +148 -0
  45. package/dist/es2019/pm-plugins/input-rules.js +29 -0
  46. package/dist/es2019/pm-plugins/insert-item-plugin.js +16 -0
  47. package/dist/es2019/pm-plugins/key.js +2 -0
  48. package/dist/es2019/pm-plugins/main.js +106 -0
  49. package/dist/es2019/pm-plugins/reducer.js +160 -0
  50. package/dist/es2019/pm-plugins/utils.js +12 -0
  51. package/dist/es2019/stats-modifier.js +33 -0
  52. package/dist/es2019/transforms/close-type-ahead.js +7 -0
  53. package/dist/es2019/transforms/open-typeahead-at-cursor.js +71 -0
  54. package/dist/es2019/transforms/set-selection-before-query.js +10 -0
  55. package/dist/es2019/ui/AssistiveText.js +88 -0
  56. package/dist/es2019/ui/InputQuery.js +393 -0
  57. package/dist/es2019/ui/TypeAheadList.js +273 -0
  58. package/dist/es2019/ui/TypeAheadListItem.js +216 -0
  59. package/dist/es2019/ui/TypeAheadPopup.js +233 -0
  60. package/dist/es2019/ui/WrapperTypeAhead.js +109 -0
  61. package/dist/es2019/ui/hooks/use-item-insert.js +112 -0
  62. package/dist/es2019/ui/hooks/use-load-items.js +41 -0
  63. package/dist/es2019/ui/hooks/use-on-force-select.js +38 -0
  64. package/dist/es2019/utils.js +126 -0
  65. package/dist/esm/api.js +209 -0
  66. package/dist/esm/commands/insert-type-ahead-item.js +198 -0
  67. package/dist/esm/commands/update-list-items.js +17 -0
  68. package/dist/esm/commands/update-query.js +21 -0
  69. package/dist/esm/commands/update-selected-index.js +21 -0
  70. package/dist/esm/index.js +1 -1
  71. package/dist/esm/insert-utils.js +101 -0
  72. package/dist/esm/messages.js +73 -0
  73. package/dist/esm/plugin.js +374 -0
  74. package/dist/esm/pm-plugins/actions.js +10 -0
  75. package/dist/esm/pm-plugins/decorations.js +141 -0
  76. package/dist/esm/pm-plugins/input-rules.js +29 -0
  77. package/dist/esm/pm-plugins/insert-item-plugin.js +16 -0
  78. package/dist/esm/pm-plugins/key.js +2 -0
  79. package/dist/esm/pm-plugins/main.js +104 -0
  80. package/dist/esm/pm-plugins/reducer.js +151 -0
  81. package/dist/esm/pm-plugins/utils.js +12 -0
  82. package/dist/esm/stats-modifier.js +35 -0
  83. package/dist/esm/transforms/close-type-ahead.js +7 -0
  84. package/dist/esm/transforms/open-typeahead-at-cursor.js +69 -0
  85. package/dist/esm/transforms/set-selection-before-query.js +12 -0
  86. package/dist/esm/ui/AssistiveText.js +115 -0
  87. package/dist/esm/ui/InputQuery.js +390 -0
  88. package/dist/esm/ui/TypeAheadList.js +276 -0
  89. package/dist/esm/ui/TypeAheadListItem.js +171 -0
  90. package/dist/esm/ui/TypeAheadPopup.js +220 -0
  91. package/dist/esm/ui/WrapperTypeAhead.js +117 -0
  92. package/dist/esm/ui/hooks/use-item-insert.js +103 -0
  93. package/dist/esm/ui/hooks/use-load-items.js +43 -0
  94. package/dist/esm/ui/hooks/use-on-force-select.js +35 -0
  95. package/dist/esm/utils.js +124 -0
  96. package/dist/types/api.d.ts +61 -0
  97. package/dist/types/commands/insert-type-ahead-item.d.ts +12 -0
  98. package/dist/types/commands/update-list-items.d.ts +3 -0
  99. package/dist/types/commands/update-query.d.ts +2 -0
  100. package/dist/types/commands/update-selected-index.d.ts +2 -0
  101. package/dist/types/index.d.ts +2 -1
  102. package/dist/types/insert-utils.d.ts +18 -0
  103. package/dist/types/messages.d.ts +72 -0
  104. package/dist/types/plugin.d.ts +10 -0
  105. package/dist/types/pm-plugins/actions.d.ts +9 -0
  106. package/dist/types/pm-plugins/decorations.d.ts +14 -0
  107. package/dist/types/pm-plugins/input-rules.d.ts +6 -0
  108. package/dist/types/pm-plugins/insert-item-plugin.d.ts +2 -0
  109. package/dist/types/pm-plugins/key.d.ts +3 -0
  110. package/dist/types/pm-plugins/main.d.ts +14 -0
  111. package/dist/types/pm-plugins/reducer.d.ts +10 -0
  112. package/dist/types/pm-plugins/utils.d.ts +4 -0
  113. package/dist/types/stats-modifier.d.ts +20 -0
  114. package/dist/types/transforms/close-type-ahead.d.ts +2 -0
  115. package/dist/types/transforms/open-typeahead-at-cursor.d.ts +11 -0
  116. package/dist/types/transforms/set-selection-before-query.d.ts +2 -0
  117. package/dist/types/ui/AssistiveText.d.ts +33 -0
  118. package/dist/types/ui/InputQuery.d.ts +26 -0
  119. package/dist/types/ui/TypeAheadList.d.ts +25 -0
  120. package/dist/types/ui/TypeAheadListItem.d.ts +18 -0
  121. package/dist/types/ui/TypeAheadPopup.d.ts +29 -0
  122. package/dist/types/ui/WrapperTypeAhead.d.ts +20 -0
  123. package/dist/types/ui/hooks/use-item-insert.d.ts +3 -0
  124. package/dist/types/ui/hooks/use-load-items.d.ts +3 -0
  125. package/dist/types/ui/hooks/use-on-force-select.d.ts +11 -0
  126. package/dist/types/utils.d.ts +27 -0
  127. package/dist/types-ts4.5/api.d.ts +61 -0
  128. package/dist/types-ts4.5/commands/insert-type-ahead-item.d.ts +12 -0
  129. package/dist/types-ts4.5/commands/update-list-items.d.ts +3 -0
  130. package/dist/types-ts4.5/commands/update-query.d.ts +2 -0
  131. package/dist/types-ts4.5/commands/update-selected-index.d.ts +2 -0
  132. package/dist/types-ts4.5/index.d.ts +2 -1
  133. package/dist/types-ts4.5/insert-utils.d.ts +18 -0
  134. package/dist/types-ts4.5/messages.d.ts +72 -0
  135. package/dist/types-ts4.5/plugin.d.ts +10 -0
  136. package/dist/types-ts4.5/pm-plugins/actions.d.ts +9 -0
  137. package/dist/types-ts4.5/pm-plugins/decorations.d.ts +14 -0
  138. package/dist/types-ts4.5/pm-plugins/input-rules.d.ts +6 -0
  139. package/dist/types-ts4.5/pm-plugins/insert-item-plugin.d.ts +2 -0
  140. package/dist/types-ts4.5/pm-plugins/key.d.ts +3 -0
  141. package/dist/types-ts4.5/pm-plugins/main.d.ts +14 -0
  142. package/dist/types-ts4.5/pm-plugins/reducer.d.ts +10 -0
  143. package/dist/types-ts4.5/pm-plugins/utils.d.ts +4 -0
  144. package/dist/types-ts4.5/stats-modifier.d.ts +20 -0
  145. package/dist/types-ts4.5/transforms/close-type-ahead.d.ts +2 -0
  146. package/dist/types-ts4.5/transforms/open-typeahead-at-cursor.d.ts +11 -0
  147. package/dist/types-ts4.5/transforms/set-selection-before-query.d.ts +2 -0
  148. package/dist/types-ts4.5/ui/AssistiveText.d.ts +33 -0
  149. package/dist/types-ts4.5/ui/InputQuery.d.ts +26 -0
  150. package/dist/types-ts4.5/ui/TypeAheadList.d.ts +25 -0
  151. package/dist/types-ts4.5/ui/TypeAheadListItem.d.ts +18 -0
  152. package/dist/types-ts4.5/ui/TypeAheadPopup.d.ts +29 -0
  153. package/dist/types-ts4.5/ui/WrapperTypeAhead.d.ts +20 -0
  154. package/dist/types-ts4.5/ui/hooks/use-item-insert.d.ts +7 -0
  155. package/dist/types-ts4.5/ui/hooks/use-load-items.d.ts +3 -0
  156. package/dist/types-ts4.5/ui/hooks/use-on-force-select.d.ts +11 -0
  157. package/dist/types-ts4.5/utils.d.ts +27 -0
  158. package/package.json +20 -27
  159. package/report.api.md +29 -1
  160. package/tmp/api-report-tmp.d.ts +26 -0
@@ -0,0 +1,393 @@
1
+ /** @jsx jsx */
2
+ import React, { Fragment, useCallback, useLayoutEffect, useRef, useState } from 'react';
3
+ import { css, jsx } from '@emotion/react';
4
+ import { useIntl } from 'react-intl-next';
5
+ import { keyName as keyNameNormalized } from 'w3c-keyname';
6
+ import { SelectItemMode } from '@atlaskit/editor-common/type-ahead';
7
+ import { browser } from '@atlaskit/editor-common/utils';
8
+ import { blockNodesVerticalMargin } from '@atlaskit/editor-shared-styles';
9
+ import { CloseSelectionOptions, TYPE_AHEAD_POPUP_CONTENT_CLASS } from '../constants';
10
+ import { TYPE_AHEAD_DECORATION_ELEMENT_ID } from '../constants';
11
+ import { typeAheadListMessages } from '../messages';
12
+ import { getPluginState } from '../utils';
13
+ import { AssistiveText } from './AssistiveText';
14
+ const querySpanStyles = css({
15
+ outline: 'none',
16
+ '& input': {
17
+ width: '5px',
18
+ border: 'none',
19
+ background: 'transparent',
20
+ padding: 0,
21
+ margin: 0,
22
+ // ED-17022 Fixes firefox caret position
23
+ fontSize: '1em',
24
+ height: blockNodesVerticalMargin,
25
+ caretColor: "var(--ds-text-accent-blue, #0052CC)"
26
+ }
27
+ });
28
+ const isNavigationKey = event => {
29
+ return ['Enter', 'Tab', 'ArrowDown', 'ArrowUp'].includes(event.key);
30
+ };
31
+ const isUndoRedoShortcut = event => {
32
+ const key = keyNameNormalized(event);
33
+ if (event.ctrlKey && key === 'y') {
34
+ return 'historyRedo';
35
+ }
36
+ if ((event.ctrlKey || event.metaKey) && event.shiftKey && key === 'Z') {
37
+ return 'historyRedo';
38
+ }
39
+ if ((event.ctrlKey || event.metaKey) && key === 'z') {
40
+ return 'historyUndo';
41
+ }
42
+ return false;
43
+ };
44
+ const getAriaLabel = (triggerPrefix, intl) => {
45
+ switch (triggerPrefix) {
46
+ case '@':
47
+ return typeAheadListMessages.mentionInputLabel;
48
+ case '/':
49
+ return typeAheadListMessages.quickInsertInputLabel;
50
+ case ':':
51
+ return typeAheadListMessages.emojiInputLabel;
52
+ default:
53
+ return typeAheadListMessages.quickInsertInputLabel;
54
+ }
55
+ };
56
+ export const InputQuery = /*#__PURE__*/React.memo(({
57
+ triggerQueryPrefix,
58
+ cancel,
59
+ onQueryChange,
60
+ onItemSelect,
61
+ selectNextItem,
62
+ selectPreviousItem,
63
+ forceFocus,
64
+ reopenQuery,
65
+ onQueryFocus,
66
+ onUndoRedo,
67
+ editorView,
68
+ items
69
+ }) => {
70
+ const ref = useRef(document.createElement('span'));
71
+ const inputRef = useRef(null);
72
+ const [query, setQuery] = useState(null);
73
+ const cleanedInputContent = useCallback(() => {
74
+ var _ref$current;
75
+ const raw = ((_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : _ref$current.textContent) || '';
76
+ return raw;
77
+ }, []);
78
+ const onKeyUp = useCallback(event => {
79
+ const text = cleanedInputContent();
80
+ onQueryChange(text);
81
+ }, [onQueryChange, cleanedInputContent]);
82
+ const [isInFocus, setInFocus] = useState(false);
83
+ const checkKeyEvent = useCallback(event => {
84
+ var _ref$current2;
85
+ const key = keyNameNormalized(event);
86
+ const sel = document.getSelection();
87
+ const raw = ((_ref$current2 = ref.current) === null || _ref$current2 === void 0 ? void 0 : _ref$current2.textContent) || '';
88
+ const text = cleanedInputContent();
89
+ let stopDefault = false;
90
+ const {
91
+ selectedIndex
92
+ } = getPluginState(editorView.state) || {};
93
+ setInFocus(true);
94
+ switch (key) {
95
+ case ' ':
96
+ // space key
97
+ if (text.length === 0) {
98
+ cancel({
99
+ forceFocusOnEditor: true,
100
+ text: ' ',
101
+ addPrefixTrigger: true,
102
+ setSelectionAt: CloseSelectionOptions.AFTER_TEXT_INSERTED
103
+ });
104
+ stopDefault = true;
105
+ }
106
+ break;
107
+ case 'Escape':
108
+ case 'PageUp':
109
+ case 'PageDown':
110
+ case 'Home':
111
+ cancel({
112
+ text,
113
+ forceFocusOnEditor: true,
114
+ addPrefixTrigger: true,
115
+ setSelectionAt: CloseSelectionOptions.AFTER_TEXT_INSERTED
116
+ });
117
+ stopDefault = true;
118
+ break;
119
+ case 'Backspace':
120
+ if (raw.length === 0 || (sel === null || sel === void 0 ? void 0 : sel.anchorOffset) === 0) {
121
+ event.stopPropagation();
122
+ event.preventDefault();
123
+ cancel({
124
+ forceFocusOnEditor: true,
125
+ text,
126
+ addPrefixTrigger: false,
127
+ setSelectionAt: CloseSelectionOptions.BEFORE_TEXT_INSERTED
128
+ });
129
+ }
130
+ break;
131
+ case 'Enter':
132
+ // ED-14758 - Under the W3C specification, any keycode sent under IME would return a keycode 229
133
+ // event.isComposing can't be used alone as this also included a virtual keyboard under a keyboardless device, therefore, it seems the best practice would be intercepting the event as below.
134
+ // Some suggested the other workaround maybe listen on`keypress` instead of `keydown`
135
+ if (!event.isComposing && event.which !== 229 && event.keyCode !== 229) {
136
+ if (selectedIndex === -1) {
137
+ /**
138
+ * TODO DTR-1401: (also see ED-17200) There are two options
139
+ * here, either
140
+ * - set the index directly to 1 in WrapperTypeAhead.tsx's
141
+ * `insertSelectedItem` at the cost of breaking some of the a11y
142
+ * focus changes,
143
+ * - or do this jank at the cost of some small analytics noise.
144
+ *
145
+ * The focus behaviour still needs cleanup
146
+ */
147
+ selectPreviousItem();
148
+ selectNextItem();
149
+ }
150
+ onItemSelect(event.shiftKey ? SelectItemMode.SHIFT_ENTER : SelectItemMode.ENTER);
151
+ }
152
+ break;
153
+ case 'Tab':
154
+ if (selectedIndex === -1) {
155
+ /**
156
+ * TODO DTR-1401: (also see ED-17200) There are two options
157
+ * here, either
158
+ * - set the index directly to 1 in WrapperTypeAhead.tsx's
159
+ * `insertSelectedItem` at the cost of breaking some of the a11y
160
+ * focus changes,
161
+ * - or do this jank at the cost of some small analytics noise.
162
+ *
163
+ */
164
+ selectPreviousItem();
165
+ selectNextItem();
166
+ }
167
+ // TODO DTR-1401: why is this calling select item when hitting tab? fix this in DTR-1401
168
+ onItemSelect(SelectItemMode.TAB);
169
+ break;
170
+ case 'ArrowDown':
171
+ selectNextItem();
172
+ break;
173
+ case 'ArrowUp':
174
+ selectPreviousItem();
175
+ break;
176
+ }
177
+ const undoRedoType = isUndoRedoShortcut(event);
178
+ if (onUndoRedo && undoRedoType && onUndoRedo(undoRedoType)) {
179
+ stopDefault = true;
180
+ }
181
+ if (isNavigationKey(event) || stopDefault) {
182
+ event.stopPropagation();
183
+ event.preventDefault();
184
+ return false;
185
+ }
186
+ }, [onUndoRedo, onItemSelect, selectNextItem, selectPreviousItem, cancel, cleanedInputContent, editorView.state]);
187
+ const onClick = useCallback(event => {
188
+ var _inputRef$current;
189
+ event.stopPropagation();
190
+ event.preventDefault();
191
+ onQueryFocus();
192
+ (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
193
+ return false;
194
+ }, [onQueryFocus]);
195
+ useLayoutEffect(() => {
196
+ if (!ref.current) {
197
+ return;
198
+ }
199
+ const {
200
+ current: element
201
+ } = ref;
202
+ const onFocusIn = event => {
203
+ onQueryFocus();
204
+ };
205
+ const keyDown = event => {
206
+ const key = keyNameNormalized(event);
207
+ if (['ArrowLeft', 'ArrowRight'].includes(key) && document.getSelection && document.getSelection()) {
208
+ var _ref$current3;
209
+ const q = ((_ref$current3 = ref.current) === null || _ref$current3 === void 0 ? void 0 : _ref$current3.textContent) || '';
210
+ const sel = document.getSelection();
211
+ const isMovingRight = sel && 'ArrowRight' === key && sel.anchorOffset === q.length;
212
+ const isMovingLeft = sel && 'ArrowLeft' === key && (sel.anchorOffset === 0 || event.metaKey);
213
+ if (!isMovingRight && !isMovingLeft) {
214
+ return;
215
+ }
216
+ cancel({
217
+ forceFocusOnEditor: true,
218
+ addPrefixTrigger: true,
219
+ text: cleanedInputContent(),
220
+ setSelectionAt: isMovingRight ? CloseSelectionOptions.AFTER_TEXT_INSERTED : CloseSelectionOptions.BEFORE_TEXT_INSERTED
221
+ });
222
+ event.preventDefault();
223
+ event.stopPropagation();
224
+ return;
225
+ }
226
+ checkKeyEvent(event);
227
+ };
228
+ const onFocusOut = event => {
229
+ var _window$getSelection;
230
+ const {
231
+ relatedTarget
232
+ } = event;
233
+
234
+ // Given the user is changing the focus
235
+ // When the target is inside the TypeAhead Popup
236
+ // Then the popup should stay open
237
+ if (relatedTarget instanceof HTMLElement && relatedTarget.closest && relatedTarget.closest(`.${TYPE_AHEAD_POPUP_CONTENT_CLASS}`)) {
238
+ return;
239
+ }
240
+
241
+ // See ED-14909: Chrome may emit focusout events where an input
242
+ // device was not directly responsible. (This rears in react v17+ consumers
243
+ // where react-managed node removal now appears to propagate focusout events to
244
+ // our event listener). As this path is strictly for click or other typeahead
245
+ // dismissals that don't involve typeahead item selection, we carve out an
246
+ // exception for Chrome-specific events where an input device was not the initiator.
247
+ if (browser.chrome && !(((_window$getSelection = window.getSelection()) === null || _window$getSelection === void 0 ? void 0 : _window$getSelection.type) === 'Range') && !('sourceCapabilities' in event && event.sourceCapabilities)) {
248
+ return;
249
+ }
250
+ cancel({
251
+ addPrefixTrigger: true,
252
+ text: cleanedInputContent(),
253
+ setSelectionAt: CloseSelectionOptions.BEFORE_TEXT_INSERTED,
254
+ forceFocusOnEditor: false
255
+ });
256
+ };
257
+ const close = () => {
258
+ cancel({
259
+ addPrefixTrigger: false,
260
+ text: '',
261
+ forceFocusOnEditor: true,
262
+ setSelectionAt: CloseSelectionOptions.BEFORE_TEXT_INSERTED
263
+ });
264
+ };
265
+ const beforeinput = e => {
266
+ var _target$textContent;
267
+ setInFocus(false);
268
+ const {
269
+ target
270
+ } = e;
271
+ if (e.isComposing || !(target instanceof HTMLElement)) {
272
+ return;
273
+ }
274
+ if (e.inputType === 'historyUndo' && ((_target$textContent = target.textContent) === null || _target$textContent === void 0 ? void 0 : _target$textContent.length) === 0) {
275
+ e.preventDefault();
276
+ e.stopPropagation();
277
+ close();
278
+ return;
279
+ }
280
+ if (e.data != null && inputRef.current === null) {
281
+ setQuery('');
282
+
283
+ // We need to change the content on Safari
284
+ // and set the cursor at the right place
285
+ if (browser.safari) {
286
+ e.preventDefault();
287
+ const dataElement = document.createTextNode(e.data);
288
+ element.appendChild(dataElement);
289
+ const sel = window.getSelection();
290
+ const range = document.createRange();
291
+ range.setStart(dataElement, dataElement.length);
292
+ range.collapse(true);
293
+ sel === null || sel === void 0 ? void 0 : sel.removeAllRanges();
294
+ sel === null || sel === void 0 ? void 0 : sel.addRange(range);
295
+ }
296
+ }
297
+ };
298
+ let onInput = null;
299
+ if (browser.safari) {
300
+ // On Safari, for reasons beyond my understanding,
301
+ // The undo behavior is totally different from other browsers
302
+ // That why we need to have an specific branch only for Safari.
303
+ const onInput = e => {
304
+ var _target$textContent2;
305
+ const {
306
+ target
307
+ } = e;
308
+ if (e.isComposing || !(target instanceof HTMLElement)) {
309
+ return;
310
+ }
311
+ if (e.inputType === 'historyUndo' && ((_target$textContent2 = target.textContent) === null || _target$textContent2 === void 0 ? void 0 : _target$textContent2.length) === 1) {
312
+ e.preventDefault();
313
+ e.stopPropagation();
314
+ close();
315
+ return;
316
+ }
317
+ };
318
+ element.addEventListener('input', onInput);
319
+ }
320
+ element.addEventListener('focusout', onFocusOut);
321
+ element.addEventListener('focusin', onFocusIn);
322
+ element.addEventListener('keydown', keyDown);
323
+ element.addEventListener('beforeinput', beforeinput);
324
+ return () => {
325
+ element.removeEventListener('focusout', onFocusOut);
326
+ element.removeEventListener('focusin', onFocusIn);
327
+ element.removeEventListener('keydown', keyDown);
328
+ element.removeEventListener('beforeinput', beforeinput);
329
+ if (browser.safari) {
330
+ element.removeEventListener('input', onInput);
331
+ }
332
+ };
333
+ }, [triggerQueryPrefix, cleanedInputContent, onQueryFocus, cancel, checkKeyEvent, editorView.state]);
334
+ useLayoutEffect(() => {
335
+ const hasReopenQuery = typeof reopenQuery === 'string' && reopenQuery.trim().length > 0;
336
+ if (ref.current && forceFocus) {
337
+ setQuery(hasReopenQuery ? reopenQuery : null);
338
+ requestAnimationFrame(() => {
339
+ if (!(ref !== null && ref !== void 0 && ref.current)) {
340
+ return;
341
+ }
342
+ const sel = window.getSelection();
343
+ if (sel && hasReopenQuery && ref.current.lastChild instanceof Text) {
344
+ const lastChild = ref.current.lastChild;
345
+ const range = document.createRange();
346
+ range.setStart(ref.current.lastChild, lastChild.length);
347
+ range.collapse(true);
348
+ sel.removeAllRanges();
349
+ sel.addRange(range);
350
+ }
351
+ ref.current.focus();
352
+ setInFocus(true);
353
+ });
354
+ }
355
+ }, [forceFocus, reopenQuery]);
356
+ const assistiveHintID = TYPE_AHEAD_DECORATION_ELEMENT_ID + '__assistiveHint';
357
+ const intl = useIntl();
358
+
359
+ /**
360
+ When we migrated to emotion from styled component, we started getting this error.
361
+ jsx-a11y/interactive-supports-focus
362
+ Task added in https://product-fabric.atlassian.net/wiki/spaces/E/pages/3182068181/Potential+improvements#Moderate-changes.
363
+ */
364
+ return jsx(Fragment, null, triggerQueryPrefix, jsx("span", {
365
+ css: querySpanStyles,
366
+ contentEditable: true,
367
+ ref: ref,
368
+ onKeyUp: onKeyUp,
369
+ onClick: onClick,
370
+ role: "combobox",
371
+ "aria-controls": TYPE_AHEAD_DECORATION_ELEMENT_ID,
372
+ "aria-autocomplete": "list",
373
+ "aria-expanded": items.length !== 0,
374
+ "aria-labelledby": assistiveHintID,
375
+ suppressContentEditableWarning: true,
376
+ "data-query-prefix": triggerQueryPrefix
377
+ }, query === null ? jsx("input", {
378
+ ref: inputRef,
379
+ type: "text"
380
+ }) : query), jsx("span", {
381
+ id: assistiveHintID,
382
+ style: {
383
+ display: 'none'
384
+ }
385
+ }, intl.formatMessage(getAriaLabel(triggerQueryPrefix, intl)), ",", intl.formatMessage(typeAheadListMessages.inputQueryAssistiveLabel)), jsx(AssistiveText, {
386
+ assistiveText: items.length === 0 ? intl.formatMessage(typeAheadListMessages.noSearchResultsLabel, {
387
+ itemsLength: items.length
388
+ }) : '',
389
+ isInFocus: items.length === 0 || isInFocus,
390
+ id: TYPE_AHEAD_DECORATION_ELEMENT_ID
391
+ }));
392
+ });
393
+ InputQuery.displayName = 'InputQuery';
@@ -0,0 +1,273 @@
1
+ /** @jsx jsx */
2
+
3
+ import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
4
+ import { css, jsx } from '@emotion/react';
5
+ import { injectIntl, useIntl } from 'react-intl-next';
6
+ import { CellMeasurer, CellMeasurerCache } from 'react-virtualized/dist/commonjs/CellMeasurer';
7
+ import { List } from 'react-virtualized/dist/commonjs/List';
8
+ import { SelectItemMode } from '@atlaskit/editor-common/type-ahead';
9
+ import { MenuGroup } from '@atlaskit/menu';
10
+ import { updateSelectedIndex } from '../commands/update-selected-index';
11
+ import { TYPE_AHEAD_DECORATION_ELEMENT_ID } from '../constants';
12
+ import { typeAheadListMessages } from '../messages';
13
+ import { getTypeAheadListAriaLabels, moveSelectedIndex } from '../utils';
14
+ import { AssistiveText } from './AssistiveText';
15
+ import { ICON_HEIGHT, ITEM_PADDING, TypeAheadListItem } from './TypeAheadListItem';
16
+ const LIST_ITEM_ESTIMATED_HEIGHT = ICON_HEIGHT + ITEM_PADDING * 2;
17
+ const LIST_WIDTH = 320;
18
+ const TypeaheadAssistiveTextPureComponent = /*#__PURE__*/React.memo(({
19
+ numberOfResults
20
+ }) => {
21
+ const intl = useIntl();
22
+ return jsx(AssistiveText, {
23
+ assistiveText: intl.formatMessage(typeAheadListMessages.searchResultsLabel, {
24
+ itemsLength: numberOfResults
25
+ })
26
+ // when the popup is open its always in focus
27
+ ,
28
+ isInFocus: true,
29
+ id: TYPE_AHEAD_DECORATION_ELEMENT_ID + '__popup'
30
+ });
31
+ });
32
+ const TypeAheadListComponent = /*#__PURE__*/React.memo(({
33
+ items,
34
+ selectedIndex,
35
+ editorView,
36
+ onItemClick,
37
+ intl,
38
+ fitHeight,
39
+ decorationElement,
40
+ triggerHandler
41
+ }) => {
42
+ var _decorationElement$qu2;
43
+ const listRef = useRef();
44
+ const listContainerRef = useRef(null);
45
+ const lastVisibleIndexes = useRef({
46
+ overscanStartIndex: 0,
47
+ overscanStopIndex: 0,
48
+ startIndex: 0,
49
+ stopIndex: 0
50
+ });
51
+ const estimatedHeight = items.length * LIST_ITEM_ESTIMATED_HEIGHT;
52
+ const [height, setHeight] = useState(Math.min(estimatedHeight, fitHeight));
53
+ const [cache, setCache] = useState(new CellMeasurerCache({
54
+ fixedWidth: true,
55
+ defaultHeight: LIST_ITEM_ESTIMATED_HEIGHT
56
+ }));
57
+ const onItemsRendered = useCallback(props => {
58
+ lastVisibleIndexes.current = props;
59
+ }, []);
60
+ const actions = useMemo(() => ({
61
+ onItemClick
62
+ }), [onItemClick]);
63
+ const isNavigationKey = event => {
64
+ return ['ArrowDown', 'ArrowUp', 'Tab', 'Enter'].includes(event.key);
65
+ };
66
+ const focusTargetElement = useCallback(() => {
67
+ var _decorationElement$qu;
68
+ //To reset the selected index
69
+ updateSelectedIndex(-1)(editorView.state, editorView.dispatch);
70
+ listRef.current.scrollToRow(0);
71
+ decorationElement === null || decorationElement === void 0 ? void 0 : (_decorationElement$qu = decorationElement.querySelector(`[role='combobox']`)) === null || _decorationElement$qu === void 0 ? void 0 : _decorationElement$qu.focus();
72
+ }, [editorView, listRef, decorationElement]);
73
+ const selectNextItem = useMemo(() => moveSelectedIndex({
74
+ editorView,
75
+ direction: 'next'
76
+ }), [editorView]);
77
+ const selectPreviousItem = useMemo(() => moveSelectedIndex({
78
+ editorView,
79
+ direction: 'previous'
80
+ }), [editorView]);
81
+ const lastVisibleStartIndex = lastVisibleIndexes.current.startIndex;
82
+ const lastVisibleStopIndex = lastVisibleIndexes.current.stopIndex;
83
+ const onScroll = useCallback(({
84
+ scrollUpdateWasRequested
85
+ }) => {
86
+ if (!scrollUpdateWasRequested) {
87
+ return;
88
+ }
89
+
90
+ // In case the user scroll to a non-visible item like press ArrowUp from the first index
91
+ // We will force the scroll calling the scrollToItem in the useEffect hook
92
+ // When the scroll happens and we render the elements,
93
+ // we still need calculate the items height and re-draw the List.
94
+ // It is possible the item selected became invisible again (because the items height changed)
95
+ // So, we need to wait for height to be calculated. Then we need to check
96
+ // if the selected item is visible or not. If it isn't visible we call the scrollToItem again.
97
+ //
98
+ // We can't do this check in the first frame because that frame is being used by the resetScreenThrottled
99
+ // to calculate each height. THen, we can schedule a new frame when this one finishs.
100
+ requestAnimationFrame(() => {
101
+ requestAnimationFrame(() => {
102
+ const isSelectedItemVisible = selectedIndex >= lastVisibleStartIndex && selectedIndex <= lastVisibleStopIndex;
103
+
104
+ //Should scroll to the list item only when the selectedIndex >= 0 and item is not visible
105
+ if (!isSelectedItemVisible && selectedIndex !== -1) {
106
+ listRef.current.scrollToRow(selectedIndex);
107
+ } else if (selectedIndex === -1) {
108
+ listRef.current.scrollToRow(0);
109
+ }
110
+ });
111
+ });
112
+ }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex]);
113
+ const onMouseMove = (event, index) => {
114
+ event.preventDefault();
115
+ event.stopPropagation();
116
+ if (selectedIndex === index) {
117
+ return;
118
+ }
119
+ updateSelectedIndex(index)(editorView.state, editorView.dispatch);
120
+ };
121
+ useLayoutEffect(() => {
122
+ if (!listRef.current) {
123
+ return;
124
+ }
125
+ const isSelectedItemVisible = selectedIndex >= lastVisibleStartIndex && selectedIndex <= lastVisibleStopIndex;
126
+
127
+ //Should scroll to the list item only when the selectedIndex >= 0 and item is not visible
128
+ if (!isSelectedItemVisible && selectedIndex !== -1) {
129
+ listRef.current.scrollToRow(selectedIndex);
130
+ } else if (selectedIndex === -1) {
131
+ listRef.current.scrollToRow(0);
132
+ }
133
+ }, [selectedIndex, lastVisibleStartIndex, lastVisibleStopIndex]);
134
+ useLayoutEffect(() => {
135
+ setCache(new CellMeasurerCache({
136
+ fixedWidth: true,
137
+ defaultHeight: LIST_ITEM_ESTIMATED_HEIGHT
138
+ }));
139
+ // When query is updated, sometimes the scroll position of the menu is not at the top
140
+ // Scrolling back to top for consistency
141
+ requestAnimationFrame(() => {
142
+ var _listContainerRef$cur;
143
+ if ((_listContainerRef$cur = listContainerRef.current) !== null && _listContainerRef$cur !== void 0 && _listContainerRef$cur.firstChild) {
144
+ listContainerRef.current.firstChild.scrollTo(0, 0);
145
+ }
146
+ });
147
+ }, [items]);
148
+ useLayoutEffect(() => {
149
+ const height = Math.min(items.reduce((prevValue, currentValue, index) => {
150
+ return prevValue + cache.rowHeight({
151
+ index: index
152
+ });
153
+ }, 0), fitHeight);
154
+ setHeight(height);
155
+ }, [items, cache, fitHeight]);
156
+ useLayoutEffect(() => {
157
+ if (!listContainerRef.current) {
158
+ return;
159
+ }
160
+ const {
161
+ current: element
162
+ } = listContainerRef;
163
+ /**
164
+ * To handle the key events on the list
165
+ * @param event
166
+ */
167
+ const handleKeyDown = event => {
168
+ if (isNavigationKey(event)) {
169
+ switch (event.key) {
170
+ case 'ArrowDown':
171
+ selectNextItem();
172
+ event.preventDefault();
173
+ event.stopPropagation();
174
+ break;
175
+ case 'ArrowUp':
176
+ selectPreviousItem();
177
+ event.preventDefault();
178
+ event.stopPropagation();
179
+ break;
180
+
181
+ // TODO DTR-1401: why is this calling item click when hitting tab? fix this in DTR-1401
182
+ case 'Tab':
183
+ //Tab key quick inserts the selected item.
184
+ onItemClick(SelectItemMode.TAB, selectedIndex);
185
+ event.preventDefault();
186
+ break;
187
+ case 'Enter':
188
+ //Enter key quick inserts the selected item.
189
+ if (!event.isComposing || event.which !== 229 && event.keyCode !== 229) {
190
+ onItemClick(event.shiftKey ? SelectItemMode.SHIFT_ENTER : SelectItemMode.ENTER, selectedIndex);
191
+ event.preventDefault();
192
+ }
193
+ break;
194
+ default:
195
+ event.preventDefault();
196
+ }
197
+ } else {
198
+ //All the remaining keys sets focus on the typeahead query(inputQuery.tsx))
199
+ focusTargetElement();
200
+ }
201
+ };
202
+ element === null || element === void 0 ? void 0 : element.addEventListener('keydown', handleKeyDown);
203
+ return () => {
204
+ element === null || element === void 0 ? void 0 : element.removeEventListener('keydown', handleKeyDown);
205
+ };
206
+ }, [editorView.state, focusTargetElement, selectNextItem, selectPreviousItem, selectedIndex, onItemClick, items.length]);
207
+ const renderRow = ({
208
+ index,
209
+ key,
210
+ style,
211
+ parent
212
+ }) => {
213
+ const currentItem = items[index];
214
+ return jsx(CellMeasurer, {
215
+ key: key,
216
+ cache: cache,
217
+ parent: parent,
218
+ columnIndex: 0,
219
+ rowIndex: index
220
+ }, jsx("div", {
221
+ style: style,
222
+ "data-index": index
223
+ }, jsx("div", {
224
+ "data-testid": `list-item-height-observed-${index}`,
225
+ onMouseMove: e => onMouseMove(e, index)
226
+ }, jsx(TypeAheadListItem, {
227
+ key: items[index].title,
228
+ item: currentItem,
229
+ itemsLength: items.length,
230
+ itemIndex: index,
231
+ selectedIndex: selectedIndex,
232
+ onItemClick: actions.onItemClick,
233
+ ariaLabel: getTypeAheadListAriaLabels(triggerHandler === null || triggerHandler === void 0 ? void 0 : triggerHandler.trigger, intl, currentItem).listItemAriaLabel
234
+ }))));
235
+ };
236
+ const popupAriaLabel = getTypeAheadListAriaLabels(triggerHandler === null || triggerHandler === void 0 ? void 0 : triggerHandler.trigger, intl).popupAriaLabel;
237
+ if (!Array.isArray(items)) {
238
+ return null;
239
+ }
240
+ const menuGroupId = ((_decorationElement$qu2 = decorationElement.querySelector(`[role='combobox']`)) === null || _decorationElement$qu2 === void 0 ? void 0 : _decorationElement$qu2.getAttribute('aria-controls')) || TYPE_AHEAD_DECORATION_ELEMENT_ID;
241
+ return jsx(MenuGroup, {
242
+ "aria-label": popupAriaLabel,
243
+ "aria-relevant": "additions removals"
244
+ }, jsx("div", {
245
+ id: menuGroupId,
246
+ ref: listContainerRef
247
+ }, jsx(List, {
248
+ rowRenderer: renderRow,
249
+ ref: listRef,
250
+ rowCount: items.length,
251
+ rowHeight: cache.rowHeight,
252
+ onRowsRendered: onItemsRendered,
253
+ width: LIST_WIDTH,
254
+ onScroll: onScroll,
255
+ height: height,
256
+ overscanRowCount: 3,
257
+ containerRole: "presentation",
258
+ role: "listbox",
259
+ css: css`
260
+ button {
261
+ padding: ${"var(--ds-space-150, 12px)"}
262
+ ${"var(--ds-space-150, 12px)"} 11px;
263
+ span:last-child span:last-child {
264
+ white-space: normal;
265
+ }
266
+ }
267
+ `
268
+ }), jsx(TypeaheadAssistiveTextPureComponent, {
269
+ numberOfResults: items.length.toString()
270
+ })));
271
+ });
272
+ export const TypeAheadList = injectIntl(TypeAheadListComponent);
273
+ TypeAheadList.displayName = 'TypeAheadList';