@atlaskit/editor-plugin-type-ahead 0.5.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 (167) hide show
  1. package/.eslintrc.js +11 -0
  2. package/CHANGELOG.md +12 -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/constants.js +15 -0
  9. package/dist/cjs/index.js +8 -1
  10. package/dist/cjs/insert-utils.js +107 -0
  11. package/dist/cjs/messages.js +79 -0
  12. package/dist/cjs/plugin.js +382 -0
  13. package/dist/cjs/pm-plugins/actions.js +16 -0
  14. package/dist/cjs/pm-plugins/decorations.js +148 -0
  15. package/dist/cjs/pm-plugins/input-rules.js +36 -0
  16. package/dist/cjs/pm-plugins/insert-item-plugin.js +22 -0
  17. package/dist/cjs/pm-plugins/key.js +8 -0
  18. package/dist/cjs/pm-plugins/main.js +110 -0
  19. package/dist/cjs/pm-plugins/reducer.js +158 -0
  20. package/dist/cjs/pm-plugins/utils.js +18 -0
  21. package/dist/cjs/stats-modifier.js +42 -0
  22. package/dist/cjs/transforms/close-type-ahead.js +13 -0
  23. package/dist/cjs/transforms/open-typeahead-at-cursor.js +75 -0
  24. package/dist/cjs/transforms/set-selection-before-query.js +18 -0
  25. package/dist/cjs/ui/AssistiveText.js +120 -0
  26. package/dist/cjs/ui/InputQuery.js +400 -0
  27. package/dist/cjs/ui/TypeAheadList.js +285 -0
  28. package/dist/cjs/ui/TypeAheadListItem.js +181 -0
  29. package/dist/cjs/ui/TypeAheadPopup.js +230 -0
  30. package/dist/cjs/ui/WrapperTypeAhead.js +127 -0
  31. package/dist/cjs/ui/hooks/use-item-insert.js +109 -0
  32. package/dist/cjs/ui/hooks/use-load-items.js +50 -0
  33. package/dist/cjs/ui/hooks/use-on-force-select.js +41 -0
  34. package/dist/cjs/utils.js +130 -0
  35. package/dist/es2019/api.js +205 -0
  36. package/dist/es2019/commands/insert-type-ahead-item.js +204 -0
  37. package/dist/es2019/commands/update-list-items.js +17 -0
  38. package/dist/es2019/commands/update-query.js +21 -0
  39. package/dist/es2019/commands/update-selected-index.js +21 -0
  40. package/dist/es2019/constants.js +9 -0
  41. package/dist/es2019/index.js +1 -1
  42. package/dist/es2019/insert-utils.js +106 -0
  43. package/dist/es2019/messages.js +73 -0
  44. package/dist/es2019/plugin.js +381 -0
  45. package/dist/es2019/pm-plugins/actions.js +10 -0
  46. package/dist/es2019/pm-plugins/decorations.js +148 -0
  47. package/dist/es2019/pm-plugins/input-rules.js +29 -0
  48. package/dist/es2019/pm-plugins/insert-item-plugin.js +16 -0
  49. package/dist/es2019/pm-plugins/key.js +2 -0
  50. package/dist/es2019/pm-plugins/main.js +106 -0
  51. package/dist/es2019/pm-plugins/reducer.js +160 -0
  52. package/dist/es2019/pm-plugins/utils.js +12 -0
  53. package/dist/es2019/stats-modifier.js +33 -0
  54. package/dist/es2019/transforms/close-type-ahead.js +7 -0
  55. package/dist/es2019/transforms/open-typeahead-at-cursor.js +71 -0
  56. package/dist/es2019/transforms/set-selection-before-query.js +10 -0
  57. package/dist/es2019/ui/AssistiveText.js +88 -0
  58. package/dist/es2019/ui/InputQuery.js +393 -0
  59. package/dist/es2019/ui/TypeAheadList.js +273 -0
  60. package/dist/es2019/ui/TypeAheadListItem.js +216 -0
  61. package/dist/es2019/ui/TypeAheadPopup.js +233 -0
  62. package/dist/es2019/ui/WrapperTypeAhead.js +109 -0
  63. package/dist/es2019/ui/hooks/use-item-insert.js +112 -0
  64. package/dist/es2019/ui/hooks/use-load-items.js +41 -0
  65. package/dist/es2019/ui/hooks/use-on-force-select.js +38 -0
  66. package/dist/es2019/utils.js +126 -0
  67. package/dist/esm/api.js +209 -0
  68. package/dist/esm/commands/insert-type-ahead-item.js +198 -0
  69. package/dist/esm/commands/update-list-items.js +17 -0
  70. package/dist/esm/commands/update-query.js +21 -0
  71. package/dist/esm/commands/update-selected-index.js +21 -0
  72. package/dist/esm/constants.js +9 -0
  73. package/dist/esm/index.js +1 -1
  74. package/dist/esm/insert-utils.js +101 -0
  75. package/dist/esm/messages.js +73 -0
  76. package/dist/esm/plugin.js +374 -0
  77. package/dist/esm/pm-plugins/actions.js +10 -0
  78. package/dist/esm/pm-plugins/decorations.js +141 -0
  79. package/dist/esm/pm-plugins/input-rules.js +29 -0
  80. package/dist/esm/pm-plugins/insert-item-plugin.js +16 -0
  81. package/dist/esm/pm-plugins/key.js +2 -0
  82. package/dist/esm/pm-plugins/main.js +104 -0
  83. package/dist/esm/pm-plugins/reducer.js +151 -0
  84. package/dist/esm/pm-plugins/utils.js +12 -0
  85. package/dist/esm/stats-modifier.js +35 -0
  86. package/dist/esm/transforms/close-type-ahead.js +7 -0
  87. package/dist/esm/transforms/open-typeahead-at-cursor.js +69 -0
  88. package/dist/esm/transforms/set-selection-before-query.js +12 -0
  89. package/dist/esm/ui/AssistiveText.js +115 -0
  90. package/dist/esm/ui/InputQuery.js +390 -0
  91. package/dist/esm/ui/TypeAheadList.js +276 -0
  92. package/dist/esm/ui/TypeAheadListItem.js +171 -0
  93. package/dist/esm/ui/TypeAheadPopup.js +220 -0
  94. package/dist/esm/ui/WrapperTypeAhead.js +117 -0
  95. package/dist/esm/ui/hooks/use-item-insert.js +103 -0
  96. package/dist/esm/ui/hooks/use-load-items.js +43 -0
  97. package/dist/esm/ui/hooks/use-on-force-select.js +35 -0
  98. package/dist/esm/utils.js +124 -0
  99. package/dist/types/api.d.ts +61 -0
  100. package/dist/types/commands/insert-type-ahead-item.d.ts +12 -0
  101. package/dist/types/commands/update-list-items.d.ts +3 -0
  102. package/dist/types/commands/update-query.d.ts +2 -0
  103. package/dist/types/commands/update-selected-index.d.ts +2 -0
  104. package/dist/types/constants.d.ts +8 -0
  105. package/dist/types/index.d.ts +2 -1
  106. package/dist/types/insert-utils.d.ts +18 -0
  107. package/dist/types/messages.d.ts +72 -0
  108. package/dist/types/plugin.d.ts +10 -0
  109. package/dist/types/pm-plugins/actions.d.ts +9 -0
  110. package/dist/types/pm-plugins/decorations.d.ts +14 -0
  111. package/dist/types/pm-plugins/input-rules.d.ts +6 -0
  112. package/dist/types/pm-plugins/insert-item-plugin.d.ts +2 -0
  113. package/dist/types/pm-plugins/key.d.ts +3 -0
  114. package/dist/types/pm-plugins/main.d.ts +14 -0
  115. package/dist/types/pm-plugins/reducer.d.ts +10 -0
  116. package/dist/types/pm-plugins/utils.d.ts +4 -0
  117. package/dist/types/stats-modifier.d.ts +20 -0
  118. package/dist/types/transforms/close-type-ahead.d.ts +2 -0
  119. package/dist/types/transforms/open-typeahead-at-cursor.d.ts +11 -0
  120. package/dist/types/transforms/set-selection-before-query.d.ts +2 -0
  121. package/dist/types/types.d.ts +64 -3
  122. package/dist/types/ui/AssistiveText.d.ts +33 -0
  123. package/dist/types/ui/InputQuery.d.ts +26 -0
  124. package/dist/types/ui/TypeAheadList.d.ts +25 -0
  125. package/dist/types/ui/TypeAheadListItem.d.ts +18 -0
  126. package/dist/types/ui/TypeAheadPopup.d.ts +29 -0
  127. package/dist/types/ui/WrapperTypeAhead.d.ts +20 -0
  128. package/dist/types/ui/hooks/use-item-insert.d.ts +3 -0
  129. package/dist/types/ui/hooks/use-load-items.d.ts +3 -0
  130. package/dist/types/ui/hooks/use-on-force-select.d.ts +11 -0
  131. package/dist/types/utils.d.ts +27 -0
  132. package/dist/types-ts4.5/api.d.ts +61 -0
  133. package/dist/types-ts4.5/commands/insert-type-ahead-item.d.ts +12 -0
  134. package/dist/types-ts4.5/commands/update-list-items.d.ts +3 -0
  135. package/dist/types-ts4.5/commands/update-query.d.ts +2 -0
  136. package/dist/types-ts4.5/commands/update-selected-index.d.ts +2 -0
  137. package/dist/types-ts4.5/constants.d.ts +8 -0
  138. package/dist/types-ts4.5/index.d.ts +2 -1
  139. package/dist/types-ts4.5/insert-utils.d.ts +18 -0
  140. package/dist/types-ts4.5/messages.d.ts +72 -0
  141. package/dist/types-ts4.5/plugin.d.ts +10 -0
  142. package/dist/types-ts4.5/pm-plugins/actions.d.ts +9 -0
  143. package/dist/types-ts4.5/pm-plugins/decorations.d.ts +14 -0
  144. package/dist/types-ts4.5/pm-plugins/input-rules.d.ts +6 -0
  145. package/dist/types-ts4.5/pm-plugins/insert-item-plugin.d.ts +2 -0
  146. package/dist/types-ts4.5/pm-plugins/key.d.ts +3 -0
  147. package/dist/types-ts4.5/pm-plugins/main.d.ts +14 -0
  148. package/dist/types-ts4.5/pm-plugins/reducer.d.ts +10 -0
  149. package/dist/types-ts4.5/pm-plugins/utils.d.ts +4 -0
  150. package/dist/types-ts4.5/stats-modifier.d.ts +20 -0
  151. package/dist/types-ts4.5/transforms/close-type-ahead.d.ts +2 -0
  152. package/dist/types-ts4.5/transforms/open-typeahead-at-cursor.d.ts +11 -0
  153. package/dist/types-ts4.5/transforms/set-selection-before-query.d.ts +2 -0
  154. package/dist/types-ts4.5/types.d.ts +64 -3
  155. package/dist/types-ts4.5/ui/AssistiveText.d.ts +33 -0
  156. package/dist/types-ts4.5/ui/InputQuery.d.ts +26 -0
  157. package/dist/types-ts4.5/ui/TypeAheadList.d.ts +25 -0
  158. package/dist/types-ts4.5/ui/TypeAheadListItem.d.ts +18 -0
  159. package/dist/types-ts4.5/ui/TypeAheadPopup.d.ts +29 -0
  160. package/dist/types-ts4.5/ui/WrapperTypeAhead.d.ts +20 -0
  161. package/dist/types-ts4.5/ui/hooks/use-item-insert.d.ts +7 -0
  162. package/dist/types-ts4.5/ui/hooks/use-load-items.d.ts +3 -0
  163. package/dist/types-ts4.5/ui/hooks/use-on-force-select.d.ts +11 -0
  164. package/dist/types-ts4.5/utils.d.ts +27 -0
  165. package/package.json +21 -28
  166. package/report.api.md +32 -1
  167. package/tmp/api-report-tmp.d.ts +29 -0
@@ -0,0 +1,216 @@
1
+ /** @jsx jsx */
2
+ import React, { useCallback, useLayoutEffect, useMemo } from 'react';
3
+ import { css, jsx } from '@emotion/react';
4
+ import { useIntl } from 'react-intl-next';
5
+ import { IconFallback } from '@atlaskit/editor-common/quick-insert';
6
+ import { SelectItemMode } from '@atlaskit/editor-common/type-ahead';
7
+ import { relativeFontSizeToBase16 } from '@atlaskit/editor-shared-styles';
8
+ import { shortcutStyle } from '@atlaskit/editor-shared-styles/shortcut';
9
+ import { ButtonItem } from '@atlaskit/menu';
10
+ import { B400, DN600, N200, N30, N800 } from '@atlaskit/theme/colors';
11
+ import { themed } from '@atlaskit/theme/components';
12
+ import { borderRadius } from '@atlaskit/theme/constants';
13
+ import { typeAheadListMessages } from '../messages';
14
+ export const ICON_HEIGHT = 40;
15
+ export const ICON_WIDTH = 40;
16
+ export const ITEM_PADDING = 12;
17
+ export const itemIcon = css`
18
+ width: ${ICON_WIDTH}px;
19
+ height: ${ICON_HEIGHT}px;
20
+ overflow: hidden;
21
+ border: 1px solid ${"var(--ds-border, rgba(223, 225, 229, 0.5))"}; /* N60 at 50% */
22
+ border-radius: ${borderRadius()}px;
23
+ box-sizing: border-box;
24
+
25
+ display: flex;
26
+ justify-content: center;
27
+ align-items: center;
28
+
29
+ div {
30
+ width: ${ICON_WIDTH}px;
31
+ height: ${ICON_HEIGHT}px;
32
+ }
33
+ `;
34
+ const itemBody = css`
35
+ display: flex;
36
+ flex-direction: row;
37
+ flex-wrap: nowrap;
38
+ justify-content: space-between;
39
+ `;
40
+ const itemText = theme => css`
41
+ white-space: initial;
42
+ color: ${themed({
43
+ light: `var(--ds-text, ${N800})`,
44
+ dark: `var(--ds-text, ${DN600})`
45
+ })(theme)};
46
+ .item-title {
47
+ line-height: 1.4;
48
+ }
49
+ .item-description {
50
+ font-size: ${relativeFontSizeToBase16(12)};
51
+ color: ${`var(--ds-text-subtlest, ${N200})`};
52
+ margin-top: 3px;
53
+ }
54
+ `;
55
+ const itemAfter = css`
56
+ flex: 0 0 auto;
57
+ `;
58
+ const customRenderItemDivStyle = css`
59
+ overflow: hidden;
60
+ &:focus {
61
+ box-shadow: inset 2px 0px 0px ${`var(--ds-border-focused, ${B400})`};
62
+ background-color: ${`var(--ds-background-neutral-subtle-hovered, ${N30})`};
63
+ outline: none;
64
+ }
65
+ `;
66
+
67
+ /**
68
+ * This CSS emulates the desired behaviour with :focus-visible for firefox.
69
+ * Firefox unfortunately does not register keyboard focus if user mouseDown and drag a typeahead item
70
+ * resulting in focus-visible style not drawn.
71
+ */
72
+ const selectionFrame = {
73
+ '& > button:focus': {
74
+ boxShadow: `inset 2px 0px 0px ${`var(--ds-border-focused, ${B400})`};`,
75
+ backgroundColor: `${`var(--ds-background-neutral-subtle-hovered, ${N30})`}`,
76
+ outline: 'none',
77
+ '&:active': {
78
+ boxShadow: 'none'
79
+ }
80
+ },
81
+ '& > button:hover': {
82
+ backgroundColor: 'inherit',
83
+ outline: 'none'
84
+ }
85
+ };
86
+ const selectedStyle = css`
87
+ background-color: ${`var(--ds-background-neutral-subtle-hovered, ${N30})`};
88
+ box-shadow: inset 2px 0px 0px ${`var(--ds-border-focused, ${B400})`};
89
+ `;
90
+ const FallbackIcon = /*#__PURE__*/React.memo(({
91
+ label
92
+ }) => {
93
+ return jsx(IconFallback, null);
94
+ });
95
+ const noop = () => {};
96
+ const AssistiveText = ({
97
+ title,
98
+ description,
99
+ shortcut
100
+ }) => {
101
+ const intl = useIntl();
102
+ const descriptionText = description ? ` ${intl.formatMessage(typeAheadListMessages.descriptionLabel)} ${description}.` : '';
103
+ const shortcutText = shortcut ? ` ${intl.formatMessage(typeAheadListMessages.shortcutLabel)} ${shortcut}.` : '';
104
+ return jsx("span", {
105
+ className: "assistive"
106
+ }, `${title}. ${descriptionText} ${shortcutText}`);
107
+ };
108
+ export const TypeAheadListItem = ({
109
+ item,
110
+ itemsLength,
111
+ selectedIndex,
112
+ onItemClick,
113
+ itemIndex,
114
+ ariaLabel
115
+ }) => {
116
+ /**
117
+ * To select and highlight the first Item when no item is selected
118
+ * However selectedIndex remains -1, So that user does not skip the first item when down arrow key is used from typeahead query(inputQuery.tsx)
119
+ */
120
+ const isSelected = itemIndex === selectedIndex || selectedIndex === -1 && itemIndex === 0;
121
+ const {
122
+ icon,
123
+ title,
124
+ render: customRenderItem
125
+ } = item;
126
+ const elementIcon = useMemo(() => {
127
+ return jsx("div", {
128
+ css: itemIcon
129
+ }, icon ? icon() : jsx(FallbackIcon, {
130
+ label: title
131
+ }));
132
+ }, [icon, title]);
133
+ const insertSelectedItem = useCallback(() => {
134
+ onItemClick(SelectItemMode.SELECTED, itemIndex);
135
+ }, [onItemClick, itemIndex]);
136
+ const customItemRef = /*#__PURE__*/React.createRef();
137
+ const buttonItemRef = /*#__PURE__*/React.createRef();
138
+ const shouldUpdateFocus = selectedIndex === itemIndex;
139
+ useLayoutEffect(() => {
140
+ if (shouldUpdateFocus) {
141
+ var _customItemRef$curren;
142
+ customItemRef === null || customItemRef === void 0 ? void 0 : (_customItemRef$curren = customItemRef.current) === null || _customItemRef$curren === void 0 ? void 0 : _customItemRef$curren.focus();
143
+ }
144
+ }, [customItemRef, shouldUpdateFocus]);
145
+ useLayoutEffect(() => {
146
+ if (shouldUpdateFocus) {
147
+ var _buttonItemRef$curren;
148
+ buttonItemRef === null || buttonItemRef === void 0 ? void 0 : (_buttonItemRef$curren = buttonItemRef.current) === null || _buttonItemRef$curren === void 0 ? void 0 : _buttonItemRef$curren.focus();
149
+ }
150
+ }, [buttonItemRef, shouldUpdateFocus]);
151
+ const customItem = useMemo(() => {
152
+ if (!customRenderItem) {
153
+ return null;
154
+ }
155
+ const Comp = customRenderItem;
156
+ const listItemClasses = [customRenderItemDivStyle, isSelected && selectedStyle];
157
+ return jsx("div", {
158
+ "aria-selected": isSelected,
159
+ role: "option",
160
+ "aria-label": ariaLabel,
161
+ "aria-setsize": itemsLength,
162
+ "aria-posinset": itemIndex,
163
+ tabIndex: 0,
164
+ css: listItemClasses,
165
+ className: `ak-typeahead-item ${isSelected ? 'typeahead-selected-item' : ''}`
166
+ //CSS classes added for test cases purpose
167
+ ,
168
+ ref: customItemRef
169
+ }, jsx("div", {
170
+ "aria-hidden": true
171
+ }, jsx(Comp, {
172
+ onClick: insertSelectedItem,
173
+ isSelected: false //The selection styles are handled in the parent div instead. Hence isSelected is made false always.
174
+ ,
175
+ onHover: noop
176
+ })));
177
+ }, [customRenderItem, isSelected, ariaLabel, itemsLength, customItemRef, insertSelectedItem, itemIndex]);
178
+ if (customItem) {
179
+ return customItem;
180
+ }
181
+ const listItemClasses = [selectionFrame, isSelected && selectedStyle];
182
+ return jsx("span", {
183
+ css: listItemClasses
184
+ }, jsx(ButtonItem, {
185
+ onClick: insertSelectedItem,
186
+ iconBefore: elementIcon,
187
+ isSelected: isSelected,
188
+ "aria-selected": isSelected,
189
+ "aria-label": title,
190
+ "aria-setsize": itemsLength,
191
+ "aria-posinset": itemIndex,
192
+ role: "option",
193
+ ref: buttonItemRef
194
+ // @ts-ignore
195
+ ,
196
+ css: listItemClasses
197
+ }, jsx("div", {
198
+ "aria-hidden": true
199
+ }, jsx("div", {
200
+ css: itemText
201
+ }, jsx("div", {
202
+ css: itemBody
203
+ }, jsx("div", {
204
+ className: "item-title"
205
+ }, item.title), jsx("div", {
206
+ css: itemAfter
207
+ }, item.keyshortcut && jsx("div", {
208
+ css: shortcutStyle
209
+ }, item.keyshortcut))), jsx("div", {
210
+ className: "item-description"
211
+ }, item.description))), jsx(AssistiveText, {
212
+ title: item.title,
213
+ description: item.description,
214
+ shortcut: item.keyshortcut
215
+ })));
216
+ };
@@ -0,0 +1,233 @@
1
+ /** @jsx jsx */
2
+ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
3
+ import { css, jsx } from '@emotion/react';
4
+ import rafSchedule from 'raf-schd';
5
+ import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
6
+ import { findOverflowScrollParent, Popup } from '@atlaskit/editor-common/ui';
7
+ import { akEditorFloatingDialogZIndex } from '@atlaskit/editor-shared-styles';
8
+ import { N0, N50A, N60A } from '@atlaskit/theme/colors';
9
+ import { borderRadius } from '@atlaskit/theme/constants';
10
+ import { CloseSelectionOptions, TYPE_AHEAD_DECORATION_DATA_ATTRIBUTE, TYPE_AHEAD_POPUP_CONTENT_CLASS } from '../constants';
11
+ import { TypeAheadList } from './TypeAheadList';
12
+ import { ITEM_PADDING } from './TypeAheadListItem';
13
+ const DEFAULT_TYPEAHEAD_MENU_HEIGHT = 380;
14
+ const typeAheadContent = css`
15
+ background: ${`var(--ds-surface-overlay, ${N0})`};
16
+ border-radius: ${borderRadius()}px;
17
+ box-shadow: ${`var(--ds-shadow-overlay, ${`0 0 1px ${N60A}, 0 4px 8px -2px ${N50A}`})`};
18
+ padding: ${"var(--ds-space-050, 4px)"} 0;
19
+ width: 320px;
20
+ max-height: 380px; /* ~5.5 visibile items */
21
+ overflow-y: auto;
22
+ -ms-overflow-style: -ms-autohiding-scrollbar;
23
+ position: relative;
24
+ `;
25
+ const Highlight = ({
26
+ state,
27
+ triggerHandler
28
+ }) => {
29
+ if (!(triggerHandler !== null && triggerHandler !== void 0 && triggerHandler.getHighlight)) {
30
+ return null;
31
+ }
32
+ return triggerHandler.getHighlight(state);
33
+ };
34
+ const OFFSET = [0, 8];
35
+ export const TypeAheadPopup = /*#__PURE__*/React.memo(props => {
36
+ const {
37
+ editorView,
38
+ triggerHandler,
39
+ anchorElement,
40
+ popupsMountPoint,
41
+ popupsBoundariesElement,
42
+ popupsScrollableElement,
43
+ items,
44
+ selectedIndex,
45
+ onItemInsert,
46
+ fireAnalyticsCallback,
47
+ isEmptyQuery,
48
+ cancel
49
+ } = props;
50
+ const ref = useRef(null);
51
+ const startTime = useMemo(() => performance.now(),
52
+ // In case those props changes
53
+ // we need to recreate the startTime
54
+ [items, isEmptyQuery, fireAnalyticsCallback, triggerHandler] // eslint-disable-line react-hooks/exhaustive-deps
55
+ );
56
+
57
+ useEffect(() => {
58
+ if (!fireAnalyticsCallback) {
59
+ return;
60
+ }
61
+ const stopTime = performance.now();
62
+ const time = stopTime - startTime;
63
+ fireAnalyticsCallback({
64
+ payload: {
65
+ action: ACTION.RENDERED,
66
+ actionSubject: ACTION_SUBJECT.TYPEAHEAD,
67
+ eventType: EVENT_TYPE.OPERATIONAL,
68
+ attributes: {
69
+ time,
70
+ items: items.length,
71
+ initial: isEmptyQuery
72
+ }
73
+ }
74
+ });
75
+ }, [startTime, items, fireAnalyticsCallback, isEmptyQuery,
76
+ // In case the current triggerHandler changes
77
+ // e.g: Inserting a mention using the quick insert
78
+ // we need to send the event again
79
+ // eslint-disable-next-line react-hooks/exhaustive-deps
80
+ triggerHandler]);
81
+ useEffect(() => {
82
+ if (!fireAnalyticsCallback) {
83
+ return;
84
+ }
85
+ fireAnalyticsCallback({
86
+ payload: {
87
+ action: ACTION.VIEWED,
88
+ actionSubject: ACTION_SUBJECT.TYPEAHEAD_ITEM,
89
+ eventType: EVENT_TYPE.OPERATIONAL,
90
+ attributes: {
91
+ index: selectedIndex,
92
+ items: items.length
93
+ }
94
+ }
95
+ });
96
+ }, [items, fireAnalyticsCallback, selectedIndex,
97
+ // In case the current triggerHandler changes
98
+ // e.g: Inserting a mention using the quick insert
99
+ // we need to send the event again
100
+ // eslint-disable-next-line react-hooks/exhaustive-deps
101
+ triggerHandler]);
102
+ const [fitHeight, setFitHeight] = useState(DEFAULT_TYPEAHEAD_MENU_HEIGHT);
103
+ const getFitHeight = useCallback(() => {
104
+ if (!anchorElement || !popupsMountPoint) {
105
+ return;
106
+ }
107
+ const target = anchorElement;
108
+ const {
109
+ top: targetTop,
110
+ height: targetHeight
111
+ } = target.getBoundingClientRect();
112
+ const boundariesElement = document.body;
113
+ const {
114
+ height: boundariesHeight,
115
+ top: boundariesTop
116
+ } = boundariesElement.getBoundingClientRect();
117
+
118
+ // Calculating the space above and space below our decoration
119
+ const spaceAbove = targetTop - (boundariesTop - boundariesElement.scrollTop);
120
+ const spaceBelow = boundariesTop + boundariesHeight - (targetTop + targetHeight);
121
+
122
+ // Keep default height if more than enough space
123
+ if (spaceBelow >= DEFAULT_TYPEAHEAD_MENU_HEIGHT) {
124
+ return setFitHeight(DEFAULT_TYPEAHEAD_MENU_HEIGHT);
125
+ }
126
+
127
+ // Determines whether typeahead will fit above or below decoration
128
+ // and return the space available.
129
+ const newFitHeight = spaceBelow >= spaceAbove ? spaceBelow : spaceAbove;
130
+
131
+ // Each typeahead item has some padding
132
+ // We want to leave some space at the top so first item
133
+ // is not partially cropped
134
+ const fitHeightWithSpace = newFitHeight - ITEM_PADDING * 2;
135
+
136
+ // Ensure typeahead height is max its default height
137
+ const minFitHeight = Math.min(DEFAULT_TYPEAHEAD_MENU_HEIGHT, fitHeightWithSpace);
138
+ return setFitHeight(minFitHeight);
139
+ }, [anchorElement, popupsMountPoint]);
140
+ const getFitHeightDebounced = rafSchedule(getFitHeight);
141
+ useLayoutEffect(() => {
142
+ const scrollableElement = popupsScrollableElement || findOverflowScrollParent(anchorElement);
143
+ getFitHeight();
144
+ window.addEventListener('resize', getFitHeightDebounced);
145
+ if (scrollableElement) {
146
+ scrollableElement.addEventListener('scroll', getFitHeightDebounced);
147
+ }
148
+ return () => {
149
+ window.removeEventListener('resize', getFitHeightDebounced);
150
+ if (scrollableElement) {
151
+ scrollableElement.removeEventListener('scroll', getFitHeightDebounced);
152
+ }
153
+ };
154
+ }, [anchorElement, popupsScrollableElement, getFitHeightDebounced, getFitHeight]);
155
+ useLayoutEffect(() => {
156
+ const focusOut = event => {
157
+ var _window$getSelection;
158
+ const {
159
+ relatedTarget
160
+ } = event;
161
+
162
+ // Given the user is changing the focus
163
+ // When the target is inside the TypeAhead Popup
164
+ // Then the popup should stay open
165
+ if (relatedTarget instanceof HTMLElement && relatedTarget.closest && (relatedTarget.closest(`.${TYPE_AHEAD_POPUP_CONTENT_CLASS}`) || relatedTarget.closest(`[data-type-ahead="${TYPE_AHEAD_DECORATION_DATA_ATTRIBUTE}"]`))) {
166
+ return;
167
+ }
168
+ if (!(((_window$getSelection = window.getSelection()) === null || _window$getSelection === void 0 ? void 0 : _window$getSelection.type) === 'Range')) {
169
+ return;
170
+ }
171
+ cancel({
172
+ addPrefixTrigger: true,
173
+ setSelectionAt: CloseSelectionOptions.AFTER_TEXT_INSERTED,
174
+ forceFocusOnEditor: false
175
+ });
176
+ };
177
+ const {
178
+ current: element
179
+ } = ref;
180
+ element === null || element === void 0 ? void 0 : element.addEventListener('focusout', focusOut);
181
+ return () => {
182
+ element === null || element === void 0 ? void 0 : element.removeEventListener('focusout', focusOut);
183
+ };
184
+ }, [ref, cancel]);
185
+
186
+ // ED-17443 When you press escape on typeahead panel, it should remove focus and close the panel
187
+ // This is the expected keyboard behaviour advised by the Accessibility team
188
+ useLayoutEffect(() => {
189
+ const escape = event => {
190
+ if (event.key === 'Escape') {
191
+ cancel({
192
+ addPrefixTrigger: true,
193
+ setSelectionAt: CloseSelectionOptions.AFTER_TEXT_INSERTED,
194
+ forceFocusOnEditor: true
195
+ });
196
+ }
197
+ };
198
+ const {
199
+ current: element
200
+ } = ref;
201
+ element === null || element === void 0 ? void 0 : element.addEventListener('keydown', escape);
202
+ return () => {
203
+ element === null || element === void 0 ? void 0 : element.removeEventListener('keydown', escape);
204
+ };
205
+ }, [ref, cancel]);
206
+ return jsx(Popup, {
207
+ zIndex: akEditorFloatingDialogZIndex,
208
+ target: anchorElement,
209
+ mountTo: popupsMountPoint,
210
+ boundariesElement: popupsBoundariesElement,
211
+ scrollableElement: popupsScrollableElement,
212
+ fitHeight: fitHeight,
213
+ fitWidth: 340,
214
+ offset: OFFSET
215
+ }, jsx("div", {
216
+ css: typeAheadContent,
217
+ tabIndex: 0,
218
+ className: TYPE_AHEAD_POPUP_CONTENT_CLASS,
219
+ ref: ref
220
+ }, jsx(Highlight, {
221
+ state: editorView.state,
222
+ triggerHandler: triggerHandler
223
+ }), jsx(TypeAheadList, {
224
+ items: items,
225
+ selectedIndex: selectedIndex,
226
+ onItemClick: onItemInsert,
227
+ fitHeight: fitHeight,
228
+ editorView: editorView,
229
+ decorationElement: anchorElement,
230
+ triggerHandler: triggerHandler
231
+ })));
232
+ });
233
+ TypeAheadPopup.displayName = 'TypeAheadPopup';
@@ -0,0 +1,109 @@
1
+ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
+ import { SelectItemMode } from '@atlaskit/editor-common/type-ahead';
3
+ import { updateQuery } from '../commands/update-query';
4
+ import { getPluginState, moveSelectedIndex } from '../utils';
5
+ import { useItemInsert } from './hooks/use-item-insert';
6
+ import { useLoadItems } from './hooks/use-load-items';
7
+ import { useOnForceSelect } from './hooks/use-on-force-select';
8
+ import { InputQuery } from './InputQuery';
9
+ export const WrapperTypeAhead = /*#__PURE__*/React.memo(({
10
+ triggerHandler,
11
+ editorView,
12
+ anchorElement,
13
+ shouldFocusCursorInsideQuery,
14
+ popupsMountPoint,
15
+ popupsBoundariesElement,
16
+ popupsScrollableElement,
17
+ createAnalyticsEvent,
18
+ inputMethod,
19
+ getDecorationPosition,
20
+ reopenQuery,
21
+ onUndoRedo
22
+ }) => {
23
+ const [closed, setClosed] = useState(false);
24
+ const [query, setQuery] = useState(reopenQuery || '');
25
+ const queryRef = useRef(query);
26
+ const editorViewRef = useRef(editorView);
27
+ const items = useLoadItems(triggerHandler, editorView, query);
28
+ useLayoutEffect(() => {
29
+ queryRef.current = query;
30
+ }, [query]);
31
+ const [onItemInsert, onTextInsert] = useItemInsert(triggerHandler, editorView, items);
32
+ const selectNextItem = useMemo(() => moveSelectedIndex({
33
+ editorView,
34
+ direction: 'next'
35
+ }), [editorView]);
36
+ const selectPreviousItem = useMemo(() => moveSelectedIndex({
37
+ editorView,
38
+ direction: 'previous'
39
+ }), [editorView]);
40
+ const cancel = useCallback(({
41
+ setSelectionAt,
42
+ addPrefixTrigger,
43
+ text,
44
+ forceFocusOnEditor
45
+ }) => {
46
+ setClosed(true);
47
+ const fullquery = addPrefixTrigger ? `${triggerHandler.trigger}${text}` : text;
48
+ onTextInsert({
49
+ forceFocusOnEditor,
50
+ setSelectionAt,
51
+ text: fullquery
52
+ });
53
+ }, [triggerHandler, onTextInsert]);
54
+ const insertSelectedItem = useCallback((mode = SelectItemMode.SELECTED) => {
55
+ const {
56
+ current: view
57
+ } = editorViewRef;
58
+ const {
59
+ selectedIndex
60
+ } = getPluginState(view.state);
61
+ setClosed(true);
62
+ queueMicrotask(() => {
63
+ onItemInsert({
64
+ mode,
65
+ index: selectedIndex,
66
+ query: queryRef.current
67
+ });
68
+ });
69
+ }, [onItemInsert]);
70
+ const showTypeAheadPopupList = useCallback(() => {}, []);
71
+ const closePopup = useCallback(() => {
72
+ setClosed(true);
73
+ }, []);
74
+ useEffect(() => {
75
+ const {
76
+ current: view
77
+ } = editorViewRef;
78
+ const pluginState = getPluginState(view.state);
79
+ if (query.length === 0 || query === (pluginState === null || pluginState === void 0 ? void 0 : pluginState.query) || !(pluginState !== null && pluginState !== void 0 && pluginState.triggerHandler)) {
80
+ return;
81
+ }
82
+ updateQuery(query)(view.state, view.dispatch);
83
+ }, [query, reopenQuery]);
84
+ useOnForceSelect({
85
+ triggerHandler,
86
+ items,
87
+ query,
88
+ editorView,
89
+ closePopup
90
+ });
91
+ if (closed) {
92
+ return null;
93
+ }
94
+ return /*#__PURE__*/React.createElement(InputQuery, {
95
+ triggerQueryPrefix: triggerHandler.trigger,
96
+ onQueryChange: setQuery,
97
+ onItemSelect: insertSelectedItem,
98
+ selectNextItem: selectNextItem,
99
+ selectPreviousItem: selectPreviousItem,
100
+ onQueryFocus: showTypeAheadPopupList,
101
+ cancel: cancel,
102
+ forceFocus: shouldFocusCursorInsideQuery,
103
+ onUndoRedo: onUndoRedo,
104
+ reopenQuery: reopenQuery,
105
+ editorView: editorView,
106
+ items: items
107
+ });
108
+ });
109
+ WrapperTypeAhead.displayName = 'WrapperTypeAhead';
@@ -0,0 +1,112 @@
1
+ import { useCallback, useLayoutEffect, useRef } from 'react';
2
+ import { SelectItemMode } from '@atlaskit/editor-common/type-ahead';
3
+ import { insertTypeAheadItem } from '../../commands/insert-type-ahead-item';
4
+ import { CloseSelectionOptions } from '../../constants';
5
+ import { closeTypeAhead } from '../../transforms/close-type-ahead';
6
+ import { setSelectionBeforeQuery } from '../../transforms/set-selection-before-query';
7
+ const insertRawQuery = ({
8
+ view,
9
+ setSelectionAt,
10
+ text,
11
+ forceFocusOnEditor
12
+ }) => {
13
+ const {
14
+ tr,
15
+ schema
16
+ } = view.state;
17
+ closeTypeAhead(tr);
18
+ if (text.length > 0) {
19
+ tr.replaceSelectionWith(schema.text(text));
20
+ if (setSelectionAt === CloseSelectionOptions.BEFORE_TEXT_INSERTED) {
21
+ setSelectionBeforeQuery(text)(tr);
22
+ }
23
+ }
24
+ view.dispatch(tr);
25
+ if (forceFocusOnEditor) {
26
+ view.focus();
27
+ }
28
+ };
29
+ export const useItemInsert = (triggerHandler, editorView, items) => {
30
+ const editorViewRef = useRef(editorView);
31
+ const itemsRef = useRef(items);
32
+ const onTextInsert = useCallback(({
33
+ setSelectionAt,
34
+ text,
35
+ forceFocusOnEditor
36
+ }) => {
37
+ if (!triggerHandler) {
38
+ return;
39
+ }
40
+ const {
41
+ current: view
42
+ } = editorViewRef;
43
+ insertRawQuery({
44
+ view,
45
+ setSelectionAt,
46
+ text,
47
+ forceFocusOnEditor
48
+ });
49
+ }, [triggerHandler]);
50
+ const onItemInsert = useCallback(({
51
+ mode,
52
+ index,
53
+ query
54
+ }) => {
55
+ const sourceListItem = itemsRef.current;
56
+ if (sourceListItem.length === 0 || !triggerHandler) {
57
+ const text = `${triggerHandler.trigger}${query}`;
58
+ onTextInsert({
59
+ forceFocusOnEditor: true,
60
+ setSelectionAt: CloseSelectionOptions.AFTER_TEXT_INSERTED,
61
+ text
62
+ });
63
+ return;
64
+ }
65
+ const itemToInsert = sourceListItem[index];
66
+ if (!itemToInsert) {
67
+ return;
68
+ }
69
+ const {
70
+ current: view
71
+ } = editorViewRef;
72
+ insertTypeAheadItem(view)({
73
+ item: itemToInsert,
74
+ handler: triggerHandler,
75
+ mode,
76
+ query,
77
+ sourceListItem
78
+ });
79
+ }, [triggerHandler, onTextInsert]);
80
+ const onItemMatch = useCallback(({
81
+ mode,
82
+ query
83
+ }) => {
84
+ const _items = itemsRef.current;
85
+ if (_items && _items.length > 1) {
86
+ return false;
87
+ }
88
+ if (_items.length === 1) {
89
+ queueMicrotask(() => {
90
+ onItemInsert({
91
+ mode,
92
+ query,
93
+ index: 0
94
+ });
95
+ });
96
+ } else {
97
+ const text = `${triggerHandler.trigger}${query}`;
98
+ queueMicrotask(() => {
99
+ onTextInsert({
100
+ forceFocusOnEditor: true,
101
+ setSelectionAt: CloseSelectionOptions.AFTER_TEXT_INSERTED,
102
+ text: mode === SelectItemMode.SPACE ? text.concat(' ') : text
103
+ });
104
+ });
105
+ }
106
+ return true;
107
+ }, [onItemInsert, triggerHandler, onTextInsert]);
108
+ useLayoutEffect(() => {
109
+ itemsRef.current = items;
110
+ }, [items]);
111
+ return [onItemInsert, onTextInsert, onItemMatch];
112
+ };