@atlaskit/link-picker 1.6.1 → 1.8.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 (57) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cjs/analytics.codegen.js +28 -0
  3. package/dist/cjs/common/constants.js +10 -0
  4. package/dist/cjs/{ui/error-boundary-fallback → common}/generic-error-svg/index.js +0 -0
  5. package/dist/cjs/{ui/error-boundary-fallback → common}/generic-error-svg/styled.js +0 -0
  6. package/dist/cjs/services/use-plugins/index.js +32 -35
  7. package/dist/cjs/services/use-search-query/index.js +41 -0
  8. package/dist/cjs/ui/error-boundary-fallback/index.js +1 -1
  9. package/dist/cjs/ui/index.js +4 -4
  10. package/dist/cjs/ui/link-picker/index.js +74 -46
  11. package/dist/cjs/ui/link-picker/link-search-error/index.js +27 -0
  12. package/dist/cjs/ui/link-picker/messages.js +10 -0
  13. package/dist/cjs/ui/link-picker/text-input/index.js +4 -10
  14. package/dist/cjs/ui/link-picker/url.js +0 -5
  15. package/dist/cjs/version.json +1 -1
  16. package/dist/es2019/analytics.codegen.js +20 -0
  17. package/dist/es2019/common/constants.js +2 -0
  18. package/dist/es2019/{ui/error-boundary-fallback → common}/generic-error-svg/index.js +0 -0
  19. package/dist/es2019/{ui/error-boundary-fallback → common}/generic-error-svg/styled.js +0 -0
  20. package/dist/es2019/services/use-plugins/index.js +28 -38
  21. package/dist/es2019/services/use-search-query/index.js +32 -0
  22. package/dist/es2019/ui/error-boundary-fallback/index.js +1 -1
  23. package/dist/es2019/ui/index.js +1 -2
  24. package/dist/es2019/ui/link-picker/index.js +65 -45
  25. package/dist/es2019/ui/link-picker/link-search-error/index.js +10 -0
  26. package/dist/es2019/ui/link-picker/messages.js +10 -0
  27. package/dist/es2019/ui/link-picker/text-input/index.js +3 -9
  28. package/dist/es2019/ui/link-picker/url.js +0 -5
  29. package/dist/es2019/version.json +1 -1
  30. package/dist/esm/analytics.codegen.js +20 -0
  31. package/dist/esm/common/constants.js +2 -0
  32. package/dist/esm/{ui/error-boundary-fallback → common}/generic-error-svg/index.js +0 -0
  33. package/dist/esm/{ui/error-boundary-fallback → common}/generic-error-svg/styled.js +0 -0
  34. package/dist/esm/services/use-plugins/index.js +33 -35
  35. package/dist/esm/services/use-search-query/index.js +32 -0
  36. package/dist/esm/ui/error-boundary-fallback/index.js +1 -1
  37. package/dist/esm/ui/index.js +1 -2
  38. package/dist/esm/ui/link-picker/index.js +67 -45
  39. package/dist/esm/ui/link-picker/link-search-error/index.js +14 -0
  40. package/dist/esm/ui/link-picker/messages.js +10 -0
  41. package/dist/esm/ui/link-picker/text-input/index.js +4 -10
  42. package/dist/esm/ui/link-picker/url.js +0 -5
  43. package/dist/esm/version.json +1 -1
  44. package/dist/types/analytics.codegen.d.ts +20 -0
  45. package/dist/types/common/constants.d.ts +2 -0
  46. package/dist/types/{ui/error-boundary-fallback → common}/generic-error-svg/index.d.ts +0 -0
  47. package/dist/types/{ui/error-boundary-fallback → common}/generic-error-svg/styled.d.ts +0 -0
  48. package/dist/types/index.d.ts +1 -1
  49. package/dist/types/services/use-plugins/index.d.ts +6 -5
  50. package/dist/types/services/use-search-query/index.d.ts +3 -0
  51. package/dist/types/ui/link-picker/index.d.ts +2 -0
  52. package/dist/types/ui/link-picker/link-search-error/index.d.ts +4 -0
  53. package/dist/types/ui/link-picker/messages.d.ts +10 -0
  54. package/dist/types/ui/link-picker/text-input/index.d.ts +0 -1
  55. package/dist/types/ui/types.d.ts +5 -2
  56. package/package.json +11 -5
  57. package/report.api.md +11 -5
@@ -29,13 +29,12 @@ var _styled = require("./styled");
29
29
 
30
30
  var _ = require("../");
31
31
 
32
- var _excluded = ["name", "label", "autoFocus", "onSubmit", "onRedo", "onUndo", "onChange", "onKeyDown", "onClear", "clearLabel", "error"];
32
+ var _excluded = ["name", "label", "autoFocus", "onRedo", "onUndo", "onChange", "onKeyDown", "onClear", "clearLabel", "error"];
33
33
 
34
34
  var PanelTextInput = function PanelTextInput(props) {
35
35
  var name = props.name,
36
36
  label = props.label,
37
37
  autoFocus = props.autoFocus,
38
- onSubmit = props.onSubmit,
39
38
  onRedo = props.onRedo,
40
39
  onUndo = props.onUndo,
41
40
  onChange = props.onChange,
@@ -61,13 +60,7 @@ var PanelTextInput = function PanelTextInput(props) {
61
60
  onChange(e.currentTarget.value);
62
61
  }, [onChange]);
63
62
  var handleKeydown = (0, _react.useCallback)(function (e) {
64
- var value = e.currentTarget.value;
65
-
66
- if (e.keyCode === 13 && onSubmit) {
67
- e.preventDefault(); // Prevent from submitting if an editor is inside a form.
68
-
69
- onSubmit(value);
70
- } else if (typeof onUndo === 'function' && (0, _utils.isUndoEvent)(e)) {
63
+ if (typeof onUndo === 'function' && (0, _utils.isUndoEvent)(e)) {
71
64
  e.preventDefault();
72
65
  onUndo();
73
66
  } else if (typeof onRedo === 'function' && (0, _utils.isRedoEvent)(e)) {
@@ -78,7 +71,7 @@ var PanelTextInput = function PanelTextInput(props) {
78
71
  if (onKeyDown) {
79
72
  onKeyDown(e);
80
73
  }
81
- }, [onSubmit, onUndo, onRedo, onKeyDown]);
74
+ }, [onUndo, onRedo, onKeyDown]);
82
75
  var handleClear = (0, _react.useCallback)(function () {
83
76
  if (inputRef.current && onClear) {
84
77
  inputRef.current.focus();
@@ -88,6 +81,7 @@ var PanelTextInput = function PanelTextInput(props) {
88
81
  var clearText = restProps.value !== '' && (0, _react2.jsx)(_tooltip.default, {
89
82
  content: clearLabel
90
83
  }, (0, _react2.jsx)("button", {
84
+ type: "button",
91
85
  css: _styled.clearTextButtonStyles,
92
86
  onClick: handleClear,
93
87
  "data-testid": _.testIds.clearUrlButton
@@ -75,11 +75,6 @@ var linkifyMatch = function linkifyMatch(text) {
75
75
 
76
76
  var isSafeUrl = function isSafeUrl(url) {
77
77
  var urlTrimmed = url.trim();
78
-
79
- if (urlTrimmed.length === 0) {
80
- return true;
81
- }
82
-
83
78
  return whitelistedURLPatterns.some(function (p) {
84
79
  return p.test(urlTrimmed);
85
80
  });
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/link-picker",
3
- "version": "1.6.1",
3
+ "version": "1.8.0",
4
4
  "sideEffects": false
5
5
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
3
+ *
4
+ * Generates analytics utilities from the package analytics spec yaml
5
+ *
6
+ * @codegen <<SignedSource::0cc1b4d9c22b31491ded3e581114ab2c>>
7
+ * @codegenCommand yarn workspace @atlaskit/link-picker run codegen-analytics
8
+ */
9
+ function createEventPayload(eventKey, attributes) {
10
+ const event = eventKey.split('.');
11
+ return {
12
+ eventType: 'ui',
13
+ actionSubject: event[0],
14
+ action: event[1],
15
+ actionSubjectId: event[2],
16
+ attributes: attributes
17
+ };
18
+ }
19
+
20
+ export default createEventPayload;
@@ -0,0 +1,2 @@
1
+ export const ANALYTICS_CHANNEL = 'media';
2
+ export const COMPONENT_NAME = 'LinkPicker';
@@ -1,6 +1,5 @@
1
- import { useEffect, useRef, useReducer, useMemo } from 'react';
1
+ import { useEffect, useReducer, useMemo, useRef, useState, useCallback } from 'react';
2
2
  import { RECENT_SEARCH_LIST_SIZE } from '../../ui/link-picker';
3
- import { isSafeUrl } from '../../ui/link-picker/url';
4
3
 
5
4
  function reducer(state, payload) {
6
5
  return { ...state,
@@ -8,56 +7,40 @@ function reducer(state, payload) {
8
7
  };
9
8
  }
10
9
 
11
- export function usePlugins(state, plugins) {
10
+ export function usePlugins(state, activeTab, plugins) {
11
+ const [retries, setRetries] = useState(0);
12
12
  const [pluginState, dispatch] = useReducer(reducer, {
13
- items: [],
13
+ items: null,
14
+ error: null,
14
15
  isLoading: false
15
16
  });
16
- const {
17
- url,
18
- selectedIndex
19
- } = state;
20
17
  const {
21
18
  items,
22
- isLoading
19
+ isLoading,
20
+ error
23
21
  } = pluginState;
24
- const activePlugin = !plugins || plugins.length === 0 ? null : plugins[state.activeTab];
25
22
  const queryVersion = useRef(0);
26
- const pluginRef = useRef(activePlugin);
27
- const selectedItem = items[selectedIndex];
28
- const isSelectedItem = (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.url) === url;
23
+ const activePlugin = plugins === null || plugins === void 0 ? void 0 : plugins[activeTab];
29
24
  useEffect(() => {
30
25
  if (!activePlugin) {
31
26
  return;
32
27
  }
33
28
 
34
- if (isSelectedItem) {
35
- return;
36
- }
37
-
38
- if (isSafeUrl(url) && url.length) {
29
+ if (!state) {
39
30
  dispatch({
40
- items: [],
41
- isLoading: false
31
+ items: null,
32
+ isLoading: false,
33
+ error: null
42
34
  });
43
35
  return;
44
36
  }
45
37
 
46
- const updates = activePlugin.resolve({
47
- query: url
38
+ const updates = activePlugin.resolve(state);
39
+ dispatch({
40
+ items: null,
41
+ isLoading: true,
42
+ error: null
48
43
  });
49
-
50
- if (pluginRef.current !== activePlugin) {
51
- dispatch({
52
- items: [],
53
- isLoading: true
54
- });
55
- } else {
56
- dispatch({
57
- isLoading: true
58
- });
59
- }
60
-
61
44
  const newQuery = ++queryVersion.current;
62
45
 
63
46
  const updateResults = async () => {
@@ -90,15 +73,17 @@ export function usePlugins(state, plugins) {
90
73
  return;
91
74
  }
92
75
  }
93
- } catch {
76
+ } catch (error) {
94
77
  dispatch({
78
+ error,
79
+ items: null,
95
80
  isLoading: false
96
81
  });
97
82
  }
98
83
  };
99
84
 
100
85
  updateResults();
101
- }, [activePlugin, queryVersion, url, isSelectedItem]);
86
+ }, [activePlugin, queryVersion, state, retries]);
102
87
  const tabs = useMemo(() => {
103
88
  if (!plugins || plugins.length <= 1) {
104
89
  return [];
@@ -108,12 +93,17 @@ export function usePlugins(state, plugins) {
108
93
  tabTitle: plugin.tabTitle
109
94
  }));
110
95
  }, [plugins]);
96
+ const handleRetry = useCallback(() => {
97
+ setRetries(prev => ++prev);
98
+ }, []);
111
99
  return {
112
100
  tabs,
113
101
  items,
114
102
  isLoading,
115
- isSelectedItem,
116
- isActivePlugin: !!activePlugin
103
+ isActivePlugin: !!activePlugin,
104
+ error,
105
+ retry: handleRetry,
106
+ errorFallback: activePlugin === null || activePlugin === void 0 ? void 0 : activePlugin.errorFallback
117
107
  };
118
108
  }
119
109
 
@@ -0,0 +1,32 @@
1
+ import { useRef } from 'react';
2
+ import { isSafeUrl } from '../../ui/link-picker/url';
3
+ export function useSearchQuery(state) {
4
+ var _queryState$current;
5
+
6
+ const queryState = useRef(null);
7
+ /*
8
+ * When state contains a valid url AND is NOT selected from results
9
+ * queryState should be null to clear the items in plugin state
10
+ */
11
+
12
+ if (isSafeUrl(state.url) && state.selectedIndex === -1) {
13
+ queryState.current = null;
14
+ }
15
+ /*
16
+ * When state contains a search term e.g. Not a valid url return a query
17
+ */
18
+
19
+
20
+ if (((_queryState$current = queryState.current) === null || _queryState$current === void 0 ? void 0 : _queryState$current.query) !== state.url && !isSafeUrl(state.url)) {
21
+ queryState.current = {
22
+ query: state.url
23
+ };
24
+ }
25
+ /*
26
+ * When state contains a valid url and is selected from the list
27
+ * return current state to prevent triger a new plugin search
28
+ */
29
+
30
+
31
+ return queryState.current;
32
+ }
@@ -2,7 +2,7 @@
2
2
  import { jsx } from '@emotion/react';
3
3
  import EmptyState from '@atlaskit/empty-state';
4
4
  import { defineMessages, useIntl } from 'react-intl-next';
5
- import GenericErrorSVG from './generic-error-svg';
5
+ import GenericErrorSVG from '../../common/generic-error-svg';
6
6
  import { rootContainerStyles } from '../link-picker/styled';
7
7
  const messages = defineMessages({
8
8
  heading: {
@@ -1,11 +1,10 @@
1
1
  import React, { memo } from 'react';
2
2
  import { AnalyticsErrorBoundary } from '@atlaskit/analytics-next';
3
3
  import { LazySuspense, lazyForPaint } from 'react-loosely-lazy';
4
+ import { COMPONENT_NAME, ANALYTICS_CHANNEL } from '../common/constants';
4
5
  import { name as packageName, version as packageVersion } from '../version.json';
5
6
  import { ErrorBoundaryFallback } from './error-boundary-fallback';
6
7
  import { LoaderFallback } from './loader-fallback';
7
- const COMPONENT_NAME = 'LinkPicker';
8
- const ANALYTICS_CHANNEL = 'media';
9
8
  const PACKAGE_DATA = {
10
9
  packageName,
11
10
  packageVersion,
@@ -1,25 +1,31 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
2
 
3
3
  /** @jsx jsx */
4
- import { useCallback, useLayoutEffect, useReducer } from 'react';
4
+ import { Fragment, useCallback, useLayoutEffect, useReducer } from 'react';
5
5
  import { jsx } from '@emotion/react';
6
6
  import { useIntl, FormattedMessage } from 'react-intl-next';
7
+ import { useAnalyticsEvents } from '@atlaskit/analytics-next';
7
8
  import Button, { ButtonGroup } from '@atlaskit/button';
8
9
  import { FormFooter } from '@atlaskit/form';
9
10
  import EditorSearchIcon from '@atlaskit/icon/glyph/editor/search';
10
11
  import Tabs, { Tab, TabList } from '@atlaskit/tabs';
11
12
  import VisuallyHidden from '@atlaskit/visually-hidden';
13
+ import createEventPayload from '../../analytics.codegen';
14
+ import { ANALYTICS_CHANNEL } from '../../common/constants';
15
+ import { usePlugins } from '../../services/use-plugins';
16
+ import { useSearchQuery } from '../../services/use-search-query';
12
17
  import { messages } from './messages';
13
18
  import PanelTextInput from './text-input';
14
19
  import LinkSearchList from './link-search-list';
15
20
  import { normalizeUrl, isSafeUrl } from './url';
16
21
  import { rootContainerStyles, searchIconStyles, listTitleStyles, tabsWrapperStyles } from './styled';
17
22
  import { browser } from './browser';
18
- import { transformTimeStamp } from './transformTimeStamp';
19
23
  import Announcer from './announcer';
20
24
  import LinkSearchNoResults from './link-search-no-results';
21
- import { usePlugins } from '../../services/use-plugins';
25
+ import { transformTimeStamp } from './transformTimeStamp';
26
+ import LinkSearchError from './link-search-error';
22
27
  export const RECENT_SEARCH_LIST_SIZE = 5;
28
+ export const CONTACT_SUPPORT_LINK = 'https://support.atlassian.com/contact/';
23
29
  export const testIds = {
24
30
  linkPicker: 'link-picker',
25
31
  urlInputField: 'link-url',
@@ -35,7 +41,8 @@ export const testIds = {
35
41
  searchResultLoadingIndicator: 'link-picker.results-loading-indicator',
36
42
  urlError: 'link-error',
37
43
  tabList: 'link-picker-tabs',
38
- tabItem: 'link-picker-tab'
44
+ tabItem: 'link-picker-tab',
45
+ searchError: 'link-search-error'
39
46
  };
40
47
  const initState = {
41
48
  url: '',
@@ -68,6 +75,11 @@ function LinkPicker({
68
75
  url: initUrl,
69
76
  displayText: initDisplayText
70
77
  }) {
78
+ var _queryState$query, _items$length, _errorFallback;
79
+
80
+ const {
81
+ createAnalyticsEvent
82
+ } = useAnalyticsEvents();
71
83
  const [state, dispatch] = useReducer(reducer, { ...initState,
72
84
  url: normalizeUrl(initUrl) || '',
73
85
  displayText: initDisplayText || ''
@@ -77,17 +89,23 @@ function LinkPicker({
77
89
  selectedIndex,
78
90
  url,
79
91
  displayText,
80
- invalidUrl
92
+ invalidUrl,
93
+ activeTab
81
94
  } = state;
82
- const isEditing = !!initUrl;
83
95
  const intl = useIntl();
96
+ const queryState = useSearchQuery(state);
84
97
  const {
85
98
  items,
86
99
  isLoading,
87
- isSelectedItem,
88
100
  isActivePlugin,
89
- tabs
90
- } = usePlugins(state, plugins);
101
+ tabs,
102
+ error,
103
+ retry,
104
+ errorFallback
105
+ } = usePlugins(queryState, activeTab, plugins);
106
+ const isEditing = !!initUrl;
107
+ const selectedItem = items === null || items === void 0 ? void 0 : items[selectedIndex];
108
+ const isSelectedItem = (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.url) === url;
91
109
  useLayoutEffect(() => {
92
110
  if (onContentResize) {
93
111
  onContentResize();
@@ -145,13 +163,13 @@ function LinkPicker({
145
163
  });
146
164
  }, []);
147
165
  const handleMouseEnterResultItem = useCallback(objectId => {
148
- const index = findIndex(items, item => item.objectId === objectId);
166
+ const index = items === null || items === void 0 ? void 0 : items.findIndex(item => item.objectId === objectId);
149
167
  dispatch({
150
- activeIndex: index
168
+ activeIndex: index !== null && index !== void 0 ? index : -1
151
169
  });
152
170
  }, [items]);
153
171
  const handleMouseLeaveResultItem = useCallback(objectId => {
154
- const index = findIndex(items, item => item.objectId === objectId); // This is to avoid updating index that was set by other mouseenter event
172
+ const index = items === null || items === void 0 ? void 0 : items.findIndex(item => item.objectId === objectId); // This is to avoid updating index that was set by other mouseenter event
155
173
 
156
174
  if (activeIndex === index) {
157
175
  dispatch({
@@ -160,6 +178,8 @@ function LinkPicker({
160
178
  }
161
179
  }, [activeIndex, items]);
162
180
  const handleInsert = useCallback((url, title, inputType) => {
181
+ const event = createAnalyticsEvent(createEventPayload('form.submitted.linkPicker', {}));
182
+ event.fire(ANALYTICS_CHANNEL);
163
183
  onSubmit({
164
184
  url,
165
185
  displayText: displayText || null,
@@ -171,22 +191,23 @@ function LinkPicker({
171
191
  rawUrl: state.url
172
192
  } : {})
173
193
  });
174
- }, [displayText, onSubmit, state.url]);
194
+ }, [displayText, onSubmit, state.url, createAnalyticsEvent]);
175
195
  const handleSelected = useCallback(objectId => {
176
- const selectedItem = items.find(item => item.objectId === objectId);
196
+ const selectedItem = items === null || items === void 0 ? void 0 : items.find(item => item.objectId === objectId);
177
197
 
178
198
  if (selectedItem) {
179
199
  const {
180
200
  url,
181
201
  name
182
202
  } = selectedItem;
203
+ dispatchEvent(new Event('submit'));
183
204
  handleInsert(url, name, 'typeAhead');
184
205
  }
185
206
  }, [handleInsert, items]);
186
- const handleSubmit = useCallback(() => {
187
- const selectedItem = items[selectedIndex];
207
+ const handleSubmit = useCallback(event => {
208
+ event.preventDefault();
188
209
 
189
- if (isSelectedItem) {
210
+ if (isSelectedItem && selectedItem) {
190
211
  return handleInsert(selectedItem.url, selectedItem.name, 'typeAhead');
191
212
  }
192
213
 
@@ -199,18 +220,18 @@ function LinkPicker({
199
220
  return dispatch({
200
221
  invalidUrl: true
201
222
  });
202
- }, [handleInsert, isSelectedItem, items, selectedIndex, url]);
223
+ }, [handleInsert, isSelectedItem, selectedItem, url]);
203
224
  const linkPlaceHolder = isActivePlugin ? messages.placeholder : messages.linkPlaceholder;
204
- const linkListTitle = url ? messages.titleResults : messages.titleRecentlyViewed;
225
+ const linkListTitle = (queryState === null || queryState === void 0 ? void 0 : (_queryState$query = queryState.query) === null || _queryState$query === void 0 ? void 0 : _queryState$query.length) !== 0 ? messages.titleResults : messages.titleRecentlyViewed;
205
226
  const insertButtonMsg = isEditing ? messages.saveButton : messages.insertButton;
206
- const noResults = isActivePlugin && !isLoading && !items.length && !isSafeUrl(url);
227
+ const noResults = (items === null || items === void 0 ? void 0 : items.length) === 0 && !error;
207
228
  const screenReaderDescriptionId = 'search-recent-links-field-description';
208
229
  const linkSearchListId = 'link-picker-search-list';
209
230
  const ariaActiveDescendant = selectedIndex > -1 ? `link-search-list-item-${selectedIndex}` : ''; // Added workaround with a screen reader Announcer specifically for VoiceOver + Safari
210
231
  // as the Aria design pattern for combobox does not work in this case
211
232
  // for details: https://a11y-internal.atlassian.net/browse/AK-740
212
233
 
213
- const screenReaderText = browser.safari && getScreenReaderText(items, selectedIndex, intl);
234
+ const screenReaderText = browser.safari && getScreenReaderText(items !== null && items !== void 0 ? items : [], selectedIndex, intl);
214
235
  const searchIcon = isActivePlugin && jsx("span", {
215
236
  css: searchIconStyles,
216
237
  "data-testid": testIds.searchIcon
@@ -218,9 +239,10 @@ function LinkPicker({
218
239
  size: "medium",
219
240
  label: ''
220
241
  }));
221
- return jsx("div", {
242
+ return jsx("form", {
222
243
  "data-testid": testIds.linkPicker,
223
- css: rootContainerStyles
244
+ css: rootContainerStyles,
245
+ onSubmit: handleSubmit
224
246
  }, screenReaderText && jsx(Announcer, {
225
247
  ariaLive: "assertive",
226
248
  text: screenReaderText,
@@ -246,7 +268,6 @@ function LinkPicker({
246
268
  "aria-describedby": screenReaderDescriptionId,
247
269
  error: invalidUrl ? intl.formatMessage(messages.linkInvalid) : null,
248
270
  onClear: handleClear,
249
- onSubmit: handleSubmit,
250
271
  onKeyDown: handleKeyDown,
251
272
  onChange: handleChangeUrl
252
273
  }), jsx(PanelTextInput, {
@@ -259,10 +280,9 @@ function LinkPicker({
259
280
  clearLabel: intl.formatMessage(messages.clearText),
260
281
  "aria-label": intl.formatMessage(messages.linkAriaLabel),
261
282
  onClear: handleClear,
262
- onSubmit: handleSubmit,
263
283
  onKeyDown: handleKeyDown,
264
284
  onChange: handleChangeText
265
- }), tabs.length > 0 && jsx("div", {
285
+ }), queryState && jsx(Fragment, null, tabs.length > 0 && jsx("div", {
266
286
  css: tabsWrapperStyles
267
287
  }, jsx(Tabs, {
268
288
  id: testIds.tabList,
@@ -276,7 +296,7 @@ function LinkPicker({
276
296
  }, jsx(TabList, null, tabs.map(tab => jsx(Tab, {
277
297
  key: tab.tabTitle,
278
298
  testId: testIds.tabItem
279
- }, tab.tabTitle))))), !!items.length && jsx("div", {
299
+ }, tab.tabTitle))))), !!(items !== null && items !== void 0 && items.length) && jsx("div", {
280
300
  css: listTitleStyles,
281
301
  id: testIds.resultListTitle,
282
302
  "data-testid": testIds.resultListTitle
@@ -284,7 +304,7 @@ function LinkPicker({
284
304
  id: "fabric.smartcard.linkpicker.suggested.results"
285
305
  }, url && jsx(FormattedMessage, _extends({}, messages.searchLinkResults, {
286
306
  values: {
287
- count: items.length
307
+ count: (_items$length = items === null || items === void 0 ? void 0 : items.length) !== null && _items$length !== void 0 ? _items$length : 0
288
308
  },
289
309
  "aria-live": "polite",
290
310
  "aria-atomic": "true"
@@ -293,18 +313,32 @@ function LinkPicker({
293
313
  ariaControls: "fabric.smartcard.linkpicker.suggested.results",
294
314
  id: linkSearchListId,
295
315
  role: "listbox",
296
- items: items,
316
+ items: items !== null && items !== void 0 ? items : [],
297
317
  isLoading: isLoading,
298
318
  selectedIndex: selectedIndex,
299
319
  activeIndex: activeIndex,
300
320
  onSelect: handleSelected,
301
321
  onMouseEnter: handleMouseEnterResultItem,
302
322
  onMouseLeave: handleMouseLeaveResultItem
303
- }), noResults && jsx(LinkSearchNoResults, {
323
+ }), error && ((_errorFallback = errorFallback === null || errorFallback === void 0 ? void 0 : errorFallback(error, retry)) !== null && _errorFallback !== void 0 ? _errorFallback : jsx(LinkSearchError, {
324
+ testId: testIds.searchError,
325
+ header: intl.formatMessage(messages.searchErrorHeader),
326
+ description: jsx(FormattedMessage, _extends({}, messages.searchErrorDescription, {
327
+ values: {
328
+ a: label => jsx(Button, {
329
+ appearance: "link",
330
+ spacing: "none",
331
+ href: CONTACT_SUPPORT_LINK,
332
+ target: "_blank",
333
+ rel: "noopener noreferrer"
334
+ }, label)
335
+ }
336
+ }))
337
+ })), noResults && jsx(LinkSearchNoResults, {
304
338
  testId: testIds.emptyResultPage,
305
339
  header: intl.formatMessage(messages.noResults),
306
340
  description: intl.formatMessage(messages.noResultsDescription)
307
- }), jsx(FormFooter, {
341
+ })), jsx(FormFooter, {
308
342
  align: "end"
309
343
  }, jsx(ButtonGroup, null, jsx(Button, {
310
344
  appearance: "default",
@@ -313,7 +347,6 @@ function LinkPicker({
313
347
  }, intl.formatMessage(messages.cancelButton)), jsx(Button, {
314
348
  type: "submit",
315
349
  appearance: "primary",
316
- onClick: handleSubmit,
317
350
  testId: testIds.insertButton
318
351
  }, intl.formatMessage(insertButtonMsg)))));
319
352
  }
@@ -332,17 +365,4 @@ function getScreenReaderText(items, selectedIndex, intl) {
332
365
  const formattedDate = [date === null || date === void 0 ? void 0 : date.pageAction, date === null || date === void 0 ? void 0 : date.dateString, date === null || date === void 0 ? void 0 : date.timeSince].filter(Boolean).join(' ');
333
366
  return [name, container, formattedDate].filter(Boolean).join(', ');
334
367
  }
335
- }
336
-
337
- function findIndex(array, predicate) {
338
- let index = -1;
339
- array.some((item, i) => {
340
- if (predicate(item)) {
341
- index = i;
342
- return true;
343
- }
344
-
345
- return false;
346
- });
347
- return index;
348
368
  }
@@ -0,0 +1,10 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ import React from 'react';
3
+ import EmptyState from '@atlaskit/empty-state';
4
+ import GenericErrorSVG from '../../../common/generic-error-svg';
5
+
6
+ const LinkSearchError = props => /*#__PURE__*/React.createElement(EmptyState, _extends({}, props, {
7
+ renderImage: () => /*#__PURE__*/React.createElement(GenericErrorSVG, null)
8
+ }));
9
+
10
+ export default LinkSearchError;
@@ -104,5 +104,15 @@ export const messages = defineMessages({
104
104
  id: 'fabric.linkPicker.button.save',
105
105
  defaultMessage: 'Save',
106
106
  description: 'Button to save edited link'
107
+ },
108
+ searchErrorHeader: {
109
+ id: 'fabric.linkPicker.search.error.heading',
110
+ defaultMessage: 'We’re having trouble loading data.',
111
+ description: 'Heading message shown when a search throws an error'
112
+ },
113
+ searchErrorDescription: {
114
+ id: 'fabric.linkPicker.search.error.description',
115
+ defaultMessage: 'Refresh the page, or contact <a>Atlassian Support</a> if this keeps happening.',
116
+ description: 'Describes possible actions when search throws an error'
107
117
  }
108
118
  });
@@ -16,7 +16,6 @@ const PanelTextInput = props => {
16
16
  name,
17
17
  label,
18
18
  autoFocus,
19
- onSubmit,
20
19
  onRedo,
21
20
  onUndo,
22
21
  onChange,
@@ -43,13 +42,7 @@ const PanelTextInput = props => {
43
42
  onChange(e.currentTarget.value);
44
43
  }, [onChange]);
45
44
  const handleKeydown = useCallback(e => {
46
- const value = e.currentTarget.value;
47
-
48
- if (e.keyCode === 13 && onSubmit) {
49
- e.preventDefault(); // Prevent from submitting if an editor is inside a form.
50
-
51
- onSubmit(value);
52
- } else if (typeof onUndo === 'function' && isUndoEvent(e)) {
45
+ if (typeof onUndo === 'function' && isUndoEvent(e)) {
53
46
  e.preventDefault();
54
47
  onUndo();
55
48
  } else if (typeof onRedo === 'function' && isRedoEvent(e)) {
@@ -60,7 +53,7 @@ const PanelTextInput = props => {
60
53
  if (onKeyDown) {
61
54
  onKeyDown(e);
62
55
  }
63
- }, [onSubmit, onUndo, onRedo, onKeyDown]);
56
+ }, [onUndo, onRedo, onKeyDown]);
64
57
  const handleClear = useCallback(() => {
65
58
  if (inputRef.current && onClear) {
66
59
  inputRef.current.focus();
@@ -70,6 +63,7 @@ const PanelTextInput = props => {
70
63
  const clearText = restProps.value !== '' && jsx(Tooltip, {
71
64
  content: clearLabel
72
65
  }, jsx("button", {
66
+ type: "button",
73
67
  css: clearTextButtonStyles,
74
68
  onClick: handleClear,
75
69
  "data-testid": testIds.clearUrlButton
@@ -64,11 +64,6 @@ const linkifyMatch = text => {
64
64
 
65
65
  export const isSafeUrl = url => {
66
66
  const urlTrimmed = url.trim();
67
-
68
- if (urlTrimmed.length === 0) {
69
- return true;
70
- }
71
-
72
67
  return whitelistedURLPatterns.some(p => p.test(urlTrimmed));
73
68
  };
74
69
  /**
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/link-picker",
3
- "version": "1.6.1",
3
+ "version": "1.8.0",
4
4
  "sideEffects": false
5
5
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
3
+ *
4
+ * Generates analytics utilities from the package analytics spec yaml
5
+ *
6
+ * @codegen <<SignedSource::0cc1b4d9c22b31491ded3e581114ab2c>>
7
+ * @codegenCommand yarn workspace @atlaskit/link-picker run codegen-analytics
8
+ */
9
+ function createEventPayload(eventKey, attributes) {
10
+ var event = eventKey.split('.');
11
+ return {
12
+ eventType: 'ui',
13
+ actionSubject: event[0],
14
+ action: event[1],
15
+ actionSubjectId: event[2],
16
+ attributes: attributes
17
+ };
18
+ }
19
+
20
+ export default createEventPayload;
@@ -0,0 +1,2 @@
1
+ export var ANALYTICS_CHANNEL = 'media';
2
+ export var COMPONENT_NAME = 'LinkPicker';