@atlaskit/editor-plugin-quick-insert 27.0.5 → 28.0.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 (23) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/cjs/ui/RegistryElementBrowser/RegistryElementBrowser.js +160 -61
  3. package/dist/cjs/ui/RegistryElementBrowser/RegistryElementBrowserSearchResults.compiled.css +1 -12
  4. package/dist/cjs/ui/RegistryElementBrowser/RegistryElementBrowserSearchResults.js +10 -39
  5. package/dist/cjs/ui/RegistryElementBrowser/index.js +14 -6
  6. package/dist/cjs/ui/RegistryElementBrowser/model.js +1 -1
  7. package/dist/es2019/ui/RegistryElementBrowser/RegistryElementBrowser.js +158 -51
  8. package/dist/es2019/ui/RegistryElementBrowser/RegistryElementBrowserSearchResults.compiled.css +1 -12
  9. package/dist/es2019/ui/RegistryElementBrowser/RegistryElementBrowserSearchResults.js +8 -36
  10. package/dist/es2019/ui/RegistryElementBrowser/index.js +13 -7
  11. package/dist/es2019/ui/RegistryElementBrowser/model.js +1 -1
  12. package/dist/esm/ui/RegistryElementBrowser/RegistryElementBrowser.js +160 -61
  13. package/dist/esm/ui/RegistryElementBrowser/RegistryElementBrowserSearchResults.compiled.css +1 -12
  14. package/dist/esm/ui/RegistryElementBrowser/RegistryElementBrowserSearchResults.js +8 -38
  15. package/dist/esm/ui/RegistryElementBrowser/index.js +15 -7
  16. package/dist/esm/ui/RegistryElementBrowser/model.js +1 -1
  17. package/dist/types/ui/RegistryElementBrowser/RegistryElementBrowser.d.ts +3 -1
  18. package/dist/types/ui/RegistryElementBrowser/model.d.ts +1 -1
  19. package/package.json +9 -9
  20. package/dist/cjs/ui/RegistryElementBrowser/NotFoundIllustration.js +0 -76
  21. package/dist/es2019/ui/RegistryElementBrowser/NotFoundIllustration.js +0 -69
  22. package/dist/esm/ui/RegistryElementBrowser/NotFoundIllustration.js +0 -69
  23. package/dist/types/ui/RegistryElementBrowser/NotFoundIllustration.d.ts +0 -1
@@ -6,6 +6,7 @@ import { useIntl } from 'react-intl';
6
6
  import IconButton from '@atlaskit/button/icon/button';
7
7
  import { messages } from '@atlaskit/editor-common/quick-insert';
8
8
  import { MENU, RECOMMENDED_SECTION } from '@atlaskit/editor-common/quick-insert/keys';
9
+ import { useQuickInsertSearchAnalytics } from '@atlaskit/editor-common/quick-insert/use-search-analytics';
9
10
  import { getMenuFooterSectionKey } from '@atlaskit/editor-common/type-ahead-get-menu-footer-section-key';
10
11
  import { getSectionOverflowItemKey } from '@atlaskit/editor-common/type-ahead-get-section-overflow-item-key';
11
12
  import Heading from '@atlaskit/heading/heading';
@@ -27,6 +28,7 @@ const categoryColumnGap = `calc(${"var(--ds-space-200, 16px)"} + ${"var(--ds-spa
27
28
  const browserLayoutStyles = null;
28
29
  const headerContentStyles = null;
29
30
  const headerSearchStyles = null;
31
+ const EMPTY_ITEMS = [];
30
32
  const resultsId = 'registry-element-browser-results';
31
33
  const modalTestId = 'registry-element-browser-modal';
32
34
  const getRenderedColumnCount = options => {
@@ -38,6 +40,7 @@ const getRenderedColumnCount = options => {
38
40
  export const RegistryElementBrowser = ({
39
41
  components,
40
42
  emptyStateHandler,
43
+ fireAnalyticsEvent,
41
44
  defaultSection,
42
45
  helpUrl,
43
46
  editorView,
@@ -50,63 +53,165 @@ export const RegistryElementBrowser = ({
50
53
  onConfirmInsert,
51
54
  onSelect
52
55
  }) => {
53
- var _model$fallbackItems$, _model$fallbackItems;
56
+ var _model$fallbackItems, _model$fallbackItems$, _model$fallbackItems2;
54
57
  const {
55
58
  formatMessage
56
59
  } = useIntl();
57
- const model = useMemo(() => createRegistryElementBrowserModel(components, {
58
- footerKey: getMenuFooterSectionKey(MENU.key),
59
- overflowKeys: new Set(components.filter(component => component.type === 'menu-section').map(component => getSectionOverflowItemKey(component.key))),
60
- recommendedSectionKey: RECOMMENDED_SECTION.key,
61
- rootKey: MENU.key
62
- }), [components]);
63
- const [query, setQuery] = useState('');
64
- const [section, setSection] = useState(() => getInitialRegistryElementBrowserSection(model, defaultSection));
65
- const [selectedKey, setSelectedKey] = useState();
60
+ const [browserState, setBrowserState] = useState({
61
+ query: '',
62
+ section: defaultSection
63
+ });
64
+ const {
65
+ query,
66
+ section,
67
+ selectedRegistration
68
+ } = browserState;
69
+ const selectedRegistrationRef = useRef(undefined);
70
+ const browserModel = useMemo(() => {
71
+ const model = createRegistryElementBrowserModel(components, {
72
+ footerKey: getMenuFooterSectionKey(MENU.key),
73
+ overflowKeys: new Set(components.filter(component => component.type === 'menu-section').map(component => getSectionOverflowItemKey(component.key))),
74
+ recommendedSectionKey: RECOMMENDED_SECTION.key,
75
+ rootKey: MENU.key
76
+ });
77
+ const effectiveSection = section && model.sections.some(({
78
+ key
79
+ }) => key === section) ? section : undefined;
80
+ return {
81
+ model,
82
+ effectiveSection,
83
+ items: getRegistryElementBrowserItems({
84
+ formatMessage,
85
+ model,
86
+ query,
87
+ section: effectiveSection
88
+ })
89
+ };
90
+ }, [components, formatMessage, query, section]);
91
+ const {
92
+ model,
93
+ effectiveSection,
94
+ items
95
+ } = browserModel;
96
+ const visibleItems = items.length === 0 && !isLoading && query.trim().length > 0 ? (_model$fallbackItems = model.fallbackItems) !== null && _model$fallbackItems !== void 0 ? _model$fallbackItems : EMPTY_ITEMS : items;
97
+ const isSelectedRegistrationCurrent = Boolean(selectedRegistration && browserState.selectionIsOffline === isOffline && visibleItems.some(({
98
+ registration
99
+ }) => registration === selectedRegistration));
100
+ const selectedKey = isSelectedRegistrationCurrent ? selectedRegistration === null || selectedRegistration === void 0 ? void 0 : selectedRegistration.key : undefined;
101
+ const invalidSection = Boolean(section && section !== effectiveSection);
102
+ const invalidSelection = Boolean(selectedRegistration && !isSelectedRegistrationCurrent);
103
+ if (isOpen && (invalidSection || invalidSelection)) {
104
+ setBrowserState({
105
+ ...browserState,
106
+ section: effectiveSection,
107
+ selectedRegistration: undefined,
108
+ selectionIsOffline: undefined,
109
+ invalidation: {
110
+ section: invalidSection ? section : undefined,
111
+ selection: invalidSelection
112
+ }
113
+ });
114
+ }
66
115
  const searchInputRef = useRef(null);
67
116
  const selectedCategoryRef = useRef(null);
117
+ const focusedCategoryRef = useRef();
118
+ const handledInvalidationRef = useRef(browserState.invalidation);
68
119
  const wasOpen = useRef(false);
69
- const previousIsOffline = useRef(isOffline);
70
120
  useEffect(() => {
71
121
  if (isOpen && !wasOpen.current) {
72
- setQuery('');
73
- setSection(getInitialRegistryElementBrowserSection(model, defaultSection));
74
- // This clears local visual state and the parent-held insertion handler together.
75
- // eslint-disable-next-line @atlassian/perf-linting/no-chain-state-updates
76
- setSelectedKey(undefined);
122
+ selectedRegistrationRef.current = undefined;
123
+ setBrowserState({
124
+ query: '',
125
+ section: getInitialRegistryElementBrowserSection(model, defaultSection),
126
+ selectedRegistration: undefined
127
+ });
77
128
  onClearSelection();
78
129
  }
79
130
  wasOpen.current = isOpen;
80
131
  }, [defaultSection, isOpen, model, onClearSelection]);
81
- const items = useMemo(() => getRegistryElementBrowserItems({
82
- formatMessage,
83
- model,
84
- query,
85
- section
86
- }), [formatMessage, model, query, section]);
87
- const resultCount = items.length || (!isLoading && query.trim() ? (_model$fallbackItems$ = (_model$fallbackItems = model.fallbackItems) === null || _model$fallbackItems === void 0 ? void 0 : _model$fallbackItems.length) !== null && _model$fallbackItems$ !== void 0 ? _model$fallbackItems$ : 0 : 0);
88
132
  useEffect(() => {
89
- if (previousIsOffline.current !== isOffline) {
90
- setSelectedKey(undefined);
91
- onClearSelection();
133
+ var _searchInputRef$curre;
134
+ const invalidation = browserState.invalidation;
135
+ if (!isOpen || !invalidation || handledInvalidationRef.current === invalidation) {
136
+ return;
137
+ }
138
+ handledInvalidationRef.current = invalidation;
139
+ selectedRegistrationRef.current = undefined;
140
+ onClearSelection();
141
+ const ownerDocument = (_searchInputRef$curre = searchInputRef.current) === null || _searchInputRef$curre === void 0 ? void 0 : _searchInputRef$curre.ownerDocument;
142
+ const activeElement = ownerDocument === null || ownerDocument === void 0 ? void 0 : ownerDocument.activeElement;
143
+ if (invalidation.section && focusedCategoryRef.current === invalidation.section && activeElement === (ownerDocument === null || ownerDocument === void 0 ? void 0 : ownerDocument.body)) {
144
+ var _selectedCategoryRef$;
145
+ (_selectedCategoryRef$ = selectedCategoryRef.current) === null || _selectedCategoryRef$ === void 0 ? void 0 : _selectedCategoryRef$.focus();
146
+ } else if (invalidation.selection && (activeElement === (ownerDocument === null || ownerDocument === void 0 ? void 0 : ownerDocument.body) || activeElement !== null && activeElement !== void 0 && activeElement.closest('[role="option"]'))) {
147
+ var _searchInputRef$curre2;
148
+ (_searchInputRef$curre2 = searchInputRef.current) === null || _searchInputRef$curre2 === void 0 ? void 0 : _searchInputRef$curre2.focus();
92
149
  }
93
- previousIsOffline.current = isOffline;
94
- }, [isOffline, onClearSelection]);
150
+ }, [browserState.invalidation, isOpen, onClearSelection]);
151
+ const resultCount = items.length || (!isLoading && query.trim() ? (_model$fallbackItems$ = (_model$fallbackItems2 = model.fallbackItems) === null || _model$fallbackItems2 === void 0 ? void 0 : _model$fallbackItems2.length) !== null && _model$fallbackItems$ !== void 0 ? _model$fallbackItems$ : 0 : 0);
152
+ const flushSearchAnalytics = useQuickInsertSearchAnalytics({
153
+ query,
154
+ resultCount: items.length,
155
+ surface: 'element-browser',
156
+ category: effectiveSection !== null && effectiveSection !== void 0 ? effectiveSection : 'all',
157
+ isOpen: isOpen && !isLoading,
158
+ fireAnalyticsEvent
159
+ });
160
+ const closeBrowser = useCallback(() => {
161
+ flushSearchAnalytics();
162
+ onClose();
163
+ }, [flushSearchAnalytics, onClose]);
164
+ const onSearchFocus = useCallback(() => {
165
+ focusedCategoryRef.current = undefined;
166
+ }, []);
167
+ const onBrowserFocusCapture = useCallback(event => {
168
+ var _category$dataset$sec;
169
+ const categories = event.currentTarget.querySelector('[data-testid="registry-element-browser-categories"]');
170
+ const category = event.target instanceof HTMLElement && categories !== null && categories !== void 0 && categories.contains(event.target) ? event.target.closest('[data-section]') : null;
171
+ focusedCategoryRef.current = category ? (_category$dataset$sec = category.dataset.section) !== null && _category$dataset$sec !== void 0 ? _category$dataset$sec : '' : undefined;
172
+ }, []);
95
173
  const onSearchChange = useCallback(event => {
96
- setQuery(event.currentTarget.value);
97
- setSelectedKey(undefined);
174
+ const nextQuery = event.currentTarget.value;
175
+ selectedRegistrationRef.current = undefined;
176
+ setBrowserState(current => ({
177
+ ...current,
178
+ query: nextQuery,
179
+ selectedRegistration: undefined
180
+ }));
98
181
  onClearSelection();
99
182
  }, [onClearSelection]);
100
183
  const onSectionClick = useCallback(event => {
101
- setQuery('');
102
- setSection(event.currentTarget.dataset.section || undefined);
103
- setSelectedKey(undefined);
184
+ const nextSection = event.currentTarget.dataset.section || undefined;
185
+ selectedRegistrationRef.current = undefined;
186
+ setBrowserState({
187
+ query: '',
188
+ section: nextSection,
189
+ selectedRegistration: undefined
190
+ });
104
191
  onClearSelection();
105
192
  }, [onClearSelection]);
106
193
  const onRegistrySelect = useCallback((key, handler) => {
107
- setSelectedKey(key);
194
+ var _model$items$find;
195
+ const registration = (_model$items$find = model.items.find(({
196
+ registration
197
+ }) => registration.key === key)) === null || _model$items$find === void 0 ? void 0 : _model$items$find.registration;
198
+ selectedRegistrationRef.current = registration;
199
+ setBrowserState(current => ({
200
+ ...current,
201
+ selectedRegistration: registration,
202
+ selectionIsOffline: isOffline
203
+ }));
108
204
  onSelect(handler);
109
- }, [onSelect]);
205
+ }, [isOffline, model.items, onSelect]);
206
+ const confirmInsert = useCallback(() => {
207
+ const selectedRegistration = selectedRegistrationRef.current;
208
+ if (!selectedRegistration || !visibleItems.some(({
209
+ registration
210
+ }) => registration === selectedRegistration)) {
211
+ return;
212
+ }
213
+ onConfirmInsert();
214
+ }, [onConfirmInsert, visibleItems]);
110
215
  const onModalOpenComplete = useCallback(node => {
111
216
  node.style.borderRadius = "var(--ds-radius-large, 8px)";
112
217
  const footer = node.querySelector(`[data-testid="${modalTestId}--footer"]`);
@@ -119,9 +224,9 @@ export const RegistryElementBrowser = ({
119
224
  }, []);
120
225
  const onKeyDown = useCallback(event => {
121
226
  if (event.key === 'Tab' && event.shiftKey && event.target instanceof HTMLElement && event.target.getAttribute('role') === 'option') {
122
- var _selectedCategoryRef$;
227
+ var _selectedCategoryRef$2;
123
228
  event.preventDefault();
124
- (_selectedCategoryRef$ = selectedCategoryRef.current) === null || _selectedCategoryRef$ === void 0 ? void 0 : _selectedCategoryRef$.focus();
229
+ (_selectedCategoryRef$2 = selectedCategoryRef.current) === null || _selectedCategoryRef$2 === void 0 ? void 0 : _selectedCategoryRef$2.focus();
125
230
  return;
126
231
  }
127
232
  if (['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
@@ -147,15 +252,15 @@ export const RegistryElementBrowser = ({
147
252
  nextOption.focus();
148
253
  nextOption.click();
149
254
  } else if (nextIndex < 0 && currentIndex >= 0) {
150
- var _searchInputRef$curre;
255
+ var _searchInputRef$curre3;
151
256
  event.preventDefault();
152
- (_searchInputRef$curre = searchInputRef.current) === null || _searchInputRef$curre === void 0 ? void 0 : _searchInputRef$curre.focus();
257
+ (_searchInputRef$curre3 = searchInputRef.current) === null || _searchInputRef$curre3 === void 0 ? void 0 : _searchInputRef$curre3.focus();
153
258
  }
154
259
  return;
155
260
  }
156
261
  if (event.key === 'Escape') {
157
262
  event.preventDefault();
158
- onClose();
263
+ closeBrowser();
159
264
  return;
160
265
  }
161
266
  if (event.key === 'Enter') {
@@ -163,22 +268,22 @@ export const RegistryElementBrowser = ({
163
268
  const focusedOption = event.target instanceof HTMLElement ? event.target.closest('[role="option"]') : null;
164
269
  const firstOption = (_event$currentTarget$ = event.currentTarget.ownerDocument.getElementById(resultsId)) === null || _event$currentTarget$ === void 0 ? void 0 : _event$currentTarget$.querySelector('[role="option"]:not([aria-disabled="true"]):not([disabled])');
165
270
  const optionToActivate = focusedOption !== null && focusedOption !== void 0 ? focusedOption : firstOption;
166
- if (selectedKey || optionToActivate) {
271
+ if (isSelectedRegistrationCurrent || optionToActivate) {
167
272
  event.preventDefault();
168
- if (!selectedKey) {
273
+ if (!isSelectedRegistrationCurrent) {
169
274
  optionToActivate === null || optionToActivate === void 0 ? void 0 : optionToActivate.click();
170
275
  }
171
- onConfirmInsert();
276
+ confirmInsert();
172
277
  }
173
278
  }
174
- }, [onClose, onConfirmInsert, selectedKey]);
279
+ }, [closeBrowser, confirmInsert, isSelectedRegistrationCurrent]);
175
280
  return /*#__PURE__*/React.createElement(ModalTransition, null, isOpen && /*#__PURE__*/React.createElement(Modal, {
176
281
  label: formatMessage({
177
282
  defaultMessage: 'Insert elements',
178
283
  id: 'editor.quick-insert.title'
179
284
  }),
180
285
  height: "664px",
181
- onClose: onClose,
286
+ onClose: closeBrowser,
182
287
  onCloseComplete: onCloseComplete,
183
288
  onOpenComplete: onModalOpenComplete,
184
289
  testId: modalTestId,
@@ -204,6 +309,7 @@ export const RegistryElementBrowser = ({
204
309
  }),
205
310
  isCompact: true,
206
311
  name: "registry-element-browser-search",
312
+ onFocus: onSearchFocus,
207
313
  onChange: onSearchChange,
208
314
  onKeyDown: onKeyDown,
209
315
  ref: searchInputRef,
@@ -212,12 +318,13 @@ export const RegistryElementBrowser = ({
212
318
  appearance: "subtle",
213
319
  icon: CrossIcon,
214
320
  label: formatMessage(messages.close),
215
- onClick: onClose,
321
+ onClick: closeBrowser,
216
322
  spacing: "compact"
217
323
  })))), /*#__PURE__*/React.createElement(ModalBody, null, /*#__PURE__*/React.createElement("div", {
324
+ onFocusCapture: onBrowserFocusCapture,
218
325
  className: ax(["_zulp1fka _18zr1b66 _1e0c11p5 _yv0e1xuv _1q511b66 _lk4ji47z"])
219
326
  }, /*#__PURE__*/React.createElement(RegistryElementBrowserCategories, {
220
- section: section,
327
+ section: effectiveSection,
221
328
  sections: model.sections,
222
329
  onSectionClick: onSectionClick,
223
330
  selectedButtonRef: selectedCategoryRef
@@ -230,15 +337,15 @@ export const RegistryElementBrowser = ({
230
337
  fallbackItems: model.fallbackItems,
231
338
  query: query,
232
339
  resultsId: resultsId,
233
- section: section,
340
+ section: effectiveSection,
234
341
  selectedKey: selectedKey,
235
342
  onKeyDown: onKeyDown,
236
343
  onSelect: onRegistrySelect
237
344
  }))), /*#__PURE__*/React.createElement(ModalFooter, null, /*#__PURE__*/React.createElement(RegistryElementBrowserFooter, {
238
345
  helpUrl: helpUrl,
239
- isInsertEnabled: Boolean(selectedKey),
240
- onClose: onClose,
241
- onConfirmInsert: onConfirmInsert,
346
+ isInsertEnabled: isSelectedRegistrationCurrent,
347
+ onClose: closeBrowser,
348
+ onConfirmInsert: confirmInsert,
242
349
  resultCount: resultCount
243
350
  }))));
244
351
  };
@@ -1,25 +1,14 @@
1
- ._11c81af2{font:var(--ds-font-heading-medium,normal 653 20px/24px "Atlassian Sans",ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",Ubuntu,"Helvetica Neue",sans-serif)}
2
1
  ._zulpu2gc{gap:var(--ds-space-100,8px)}
3
- ._yyhyjvu9{grid-column:1/-1}
4
- ._15a5nqa1{border-top-style:solid}
5
- ._19pk1ejb{margin-top:var(--ds-space-300,24px)}
2
+ ._yyhyjvu9{grid-column:1/-1}._15a5nqa1{border-top-style:solid}
6
3
  ._19pkpxbi{margin-top:var(--ds-space-200,1pc)}
7
- ._19pkutpp{margin-top:var(--ds-space-150,9pt)}
8
- ._1bah1h6o{justify-content:center}
9
4
  ._1bsb1osq{width:100%}
10
5
  ._1e0c11p5{display:grid}
11
- ._1e0c1txw{display:flex}
12
6
  ._1i531l7x{border-top-color:var(--ds-border,#0b120e24)}
13
7
  ._1ul9idpf{min-width:0}
14
- ._2lx21bp4{flex-direction:column}
15
- ._4cvr1h6o{align-items:center}
16
8
  ._4cvrv2br{align-items:start}
17
9
  ._ae4vv2br{align-content:start}
18
10
  ._ca0qpxbi{padding-top:var(--ds-space-200,1pc)}
19
- ._p12f1kdj{max-width:25pc}
20
- ._syazi7uo{color:var(--ds-text,#292a2e)}
21
11
  ._uwhke4h9{border-top-width:var(--ds-border-width,1px)}
22
- ._y3gn1h6o{text-align:center}
23
12
  ._yv0ei47z{grid-template-columns:minmax(0,1fr)}
24
13
  @media (min-width:550px) and (max-width:1023px){._16409sye{grid-template-columns:repeat(2,minmax(0,1fr))}}
25
14
  @media (min-width:1024px){._g98o1koq{grid-template-columns:repeat(3,232px)}}
@@ -3,22 +3,16 @@ import "./RegistryElementBrowserSearchResults.compiled.css";
3
3
  import { ax, ix } from "@compiled/react/runtime";
4
4
  import React, { useMemo } from 'react';
5
5
  import { useIntl } from 'react-intl';
6
- import LinkButton from '@atlaskit/button/link';
6
+ import { QuickInsertEmptyState } from '@atlaskit/editor-common/quick-insert/empty-state';
7
7
  import { QuickInsertProvider } from '@atlaskit/editor-common/quick-insert/provider';
8
- import { Text } from '@atlaskit/primitives/compiled';
9
8
 
10
9
  // oxlint-disable-next-line eslint/no-redeclare
11
10
 
12
- import NotFoundIllustration from './NotFoundIllustration';
13
11
  const resultsGridStyles = null;
14
12
  const emptyStateGridItemStyles = null;
15
13
  const styles = {
16
14
  fallbackMarketplace: "_1i531l7x _15a5nqa1 _uwhke4h9 _19pkpxbi _ca0qpxbi"
17
15
  };
18
- const emptyStateWrapperStyles = null;
19
- const emptyStateHeadingStyles = null;
20
- const emptyStateSubheadingStyles = null;
21
- const emptyStateLinkStyles = null;
22
16
  const RegistryElementBrowserItem = ({
23
17
  editorView,
24
18
  isOffline,
@@ -62,7 +56,6 @@ export const RegistryElementBrowserSearchResults = ({
62
56
  onKeyDown,
63
57
  onSelect
64
58
  }) => {
65
- var _emptyStateHandler;
66
59
  const {
67
60
  formatMessage
68
61
  } = useIntl();
@@ -73,35 +66,12 @@ export const RegistryElementBrowserSearchResults = ({
73
66
  });
74
67
  const displayedItems = hasFallbackItems && fallbackItems ? fallbackItems : items;
75
68
  const shouldRenderEmptyState = items.length === 0 && !isLoading;
76
- const emptyState = shouldRenderEmptyState ? (_emptyStateHandler = emptyStateHandler === null || emptyStateHandler === void 0 ? void 0 : emptyStateHandler({
69
+ const emptyState = shouldRenderEmptyState ? emptyStateHandler === null || emptyStateHandler === void 0 ? void 0 : emptyStateHandler({
77
70
  mode: 'full',
78
71
  searchTerm: query,
79
72
  selectedCategory: section,
80
- ...(hasFallbackItems ? {
81
- showNoResultsMessage: false
82
- } : {})
83
- })) !== null && _emptyStateHandler !== void 0 ? _emptyStateHandler : /*#__PURE__*/React.createElement("div", {
84
- className: ax(["_4cvr1h6o _1e0c1txw _2lx21bp4 _1bah1h6o _1bsb1osq"])
85
- }, /*#__PURE__*/React.createElement(NotFoundIllustration, null), /*#__PURE__*/React.createElement("div", {
86
- className: ax(["_11c81af2 _syazi7uo _19pk1ejb"])
87
- }, formatMessage({
88
- defaultMessage: 'Nothing matches your search',
89
- id: 'editor.quick-insert.empty'
90
- })), /*#__PURE__*/React.createElement("div", {
91
- className: ax(["_19pkpxbi _p12f1kdj _y3gn1h6o"])
92
- }, /*#__PURE__*/React.createElement(Text, null, formatMessage({
93
- defaultMessage: 'Try searching with a different term or discover new apps for Atlassian products.',
94
- id: 'editor.quick-insert.empty-description'
95
- })), /*#__PURE__*/React.createElement("div", {
96
- className: ax(["_19pkutpp"])
97
- }, /*#__PURE__*/React.createElement(LinkButton, {
98
- appearance: "primary",
99
- href: "https://marketplace.atlassian.com/search?category=Macros&hosting=cloud&product=confluence",
100
- target: "_blank"
101
- }, formatMessage({
102
- defaultMessage: 'Explore Atlassian Marketplace',
103
- id: 'editor.quick-insert.explore-marketplace'
104
- }))))) : null;
73
+ showNoResultsMessage: false
74
+ }) : null;
105
75
  return /*#__PURE__*/React.createElement("div", {
106
76
  "data-testid": "registry-element-browser-results",
107
77
  id: resultsId,
@@ -125,7 +95,9 @@ export const RegistryElementBrowserSearchResults = ({
125
95
  item: item,
126
96
  key: item.registration.key,
127
97
  onSelect: onSelect
128
- }))), emptyState && /*#__PURE__*/React.createElement("div", {
129
- className: ax(["_yyhyjvu9 _1ul9idpf _1bsb1osq", hasFallbackItems && styles.fallbackMarketplace])
98
+ }))), shouldRenderEmptyState && !hasFallbackItems && query.trim().length > 0 && /*#__PURE__*/React.createElement(QuickInsertEmptyState, {
99
+ mode: "full"
100
+ }), emptyState && /*#__PURE__*/React.createElement("div", {
101
+ className: ax(["_yyhyjvu9 _1ul9idpf _1bsb1osq", styles.fallbackMarketplace])
130
102
  }, emptyState));
131
103
  };
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
1
+ import React, { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';
2
2
  import { ACTION, ACTION_SUBJECT, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics/types/enums';
3
3
  import { isOfflineMode } from '@atlaskit/editor-common/connectivity/isOfflineMode';
4
4
  import { insertSelectedItem } from '@atlaskit/editor-common/insert';
@@ -7,12 +7,14 @@ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/useSha
7
7
  import { closeElementBrowser } from '../../pm-plugins/commands';
8
8
  import { pluginKey } from '../../pm-plugins/plugin-key';
9
9
  import { RegistryElementBrowser } from './RegistryElementBrowser';
10
+ const EMPTY_COMPONENTS = [];
11
+ const subscribeToNothing = () => () => {};
10
12
  export const RegistryElementBrowserContainer = ({
11
13
  editorView,
12
14
  pluginInjectionAPI,
13
15
  helpUrl
14
16
  }) => {
15
- var _pluginKey$getState;
17
+ var _pluginInjectionAPI$u2, _pluginInjectionAPI$u3, _pluginInjectionAPI$a2, _pluginKey$getState;
16
18
  const {
17
19
  emptyStateHandler: currentEmptyStateHandler,
18
20
  isElementBrowserOpen,
@@ -25,12 +27,15 @@ export const RegistryElementBrowserContainer = ({
25
27
  mode: (_state$connectivitySt = state.connectivityState) === null || _state$connectivitySt === void 0 ? void 0 : _state$connectivitySt.mode
26
28
  };
27
29
  });
28
- // Snapshot once per open, during render, so the browser's open-time section reset sees the
29
- // registry for this open rather than the previous (or empty) snapshot.
30
- const components = useMemo(() => {
31
- var _pluginInjectionAPI$u, _pluginInjectionAPI$u2;
32
- return isElementBrowserOpen ? (_pluginInjectionAPI$u = pluginInjectionAPI === null || pluginInjectionAPI === void 0 ? void 0 : (_pluginInjectionAPI$u2 = pluginInjectionAPI.uiControlRegistry) === null || _pluginInjectionAPI$u2 === void 0 ? void 0 : _pluginInjectionAPI$u2.actions.getComponents(MENU.key)) !== null && _pluginInjectionAPI$u !== void 0 ? _pluginInjectionAPI$u : [] : [];
30
+ const getRegistrySnapshot = useCallback(() => {
31
+ var _pluginInjectionAPI$u;
32
+ if (!isElementBrowserOpen) {
33
+ return EMPTY_COMPONENTS;
34
+ }
35
+ const components = pluginInjectionAPI === null || pluginInjectionAPI === void 0 ? void 0 : (_pluginInjectionAPI$u = pluginInjectionAPI.uiControlRegistry) === null || _pluginInjectionAPI$u === void 0 ? void 0 : _pluginInjectionAPI$u.actions.getComponents(MENU.key);
36
+ return components !== null && components !== void 0 && components.length ? components : EMPTY_COMPONENTS;
33
37
  }, [isElementBrowserOpen, pluginInjectionAPI]);
38
+ const components = useSyncExternalStore(isElementBrowserOpen ? (_pluginInjectionAPI$u2 = pluginInjectionAPI === null || pluginInjectionAPI === void 0 ? void 0 : (_pluginInjectionAPI$u3 = pluginInjectionAPI.uiControlRegistry) === null || _pluginInjectionAPI$u3 === void 0 ? void 0 : _pluginInjectionAPI$u3.actions.subscribe) !== null && _pluginInjectionAPI$u2 !== void 0 ? _pluginInjectionAPI$u2 : subscribeToNothing : subscribeToNothing, getRegistrySnapshot, getRegistrySnapshot);
34
39
  const [emptyStateHandler, setEmptyStateHandler] = useState();
35
40
  const wasOpen = useRef(false);
36
41
  const selectionHandler = useRef(undefined);
@@ -111,6 +116,7 @@ export const RegistryElementBrowserContainer = ({
111
116
  }, [onClose]);
112
117
  return /*#__PURE__*/React.createElement(RegistryElementBrowser, {
113
118
  components: components,
119
+ fireAnalyticsEvent: pluginInjectionAPI === null || pluginInjectionAPI === void 0 ? void 0 : (_pluginInjectionAPI$a2 = pluginInjectionAPI.analytics) === null || _pluginInjectionAPI$a2 === void 0 ? void 0 : _pluginInjectionAPI$a2.actions.fireAnalyticsEvent,
114
120
  emptyStateHandler: emptyStateHandler,
115
121
  helpUrl: helpUrl,
116
122
  defaultSection: (_pluginKey$getState = pluginKey.getState(editorView.state)) === null || _pluginKey$getState === void 0 ? void 0 : _pluginKey$getState.elementBrowserInitialCategory,
@@ -35,7 +35,7 @@ const getDescendantItems = (components, parentKey, ancestorRanks, sectionKey) =>
35
35
  });
36
36
 
37
37
  /**
38
- * Builds the Element Browser's immutable open-time view of the Quick Insert registry.
38
+ * Builds the Element Browser's view of the resolved Quick Insert registry.
39
39
  * The caller provides only components rooted at the Quick Insert menu surface.
40
40
  */
41
41
  export const createRegistryElementBrowserModel = (components, {