@atlaskit/link-picker 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cjs/services/use-plugins/index.js +201 -0
  3. package/dist/cjs/ui/link-picker/index.js +308 -599
  4. package/dist/cjs/ui/link-picker/link-search-list/index.js +54 -82
  5. package/dist/cjs/ui/link-picker/list-item/index.js +5 -3
  6. package/dist/cjs/ui/link-picker/styled.js +5 -3
  7. package/dist/cjs/ui/link-picker/text-input/index.js +6 -4
  8. package/dist/cjs/version.json +1 -1
  9. package/dist/es2019/services/use-plugins/index.js +122 -0
  10. package/dist/es2019/ui/link-picker/index.js +288 -467
  11. package/dist/es2019/ui/link-picker/link-search-list/index.js +49 -50
  12. package/dist/es2019/ui/link-picker/list-item/index.js +4 -3
  13. package/dist/es2019/ui/link-picker/styled.js +4 -0
  14. package/dist/es2019/ui/link-picker/text-input/index.js +5 -4
  15. package/dist/es2019/version.json +1 -1
  16. package/dist/esm/services/use-plugins/index.js +187 -0
  17. package/dist/esm/ui/link-picker/index.js +298 -601
  18. package/dist/esm/ui/link-picker/link-search-list/index.js +51 -79
  19. package/dist/esm/ui/link-picker/list-item/index.js +4 -3
  20. package/dist/esm/ui/link-picker/styled.js +3 -2
  21. package/dist/esm/ui/link-picker/text-input/index.js +5 -4
  22. package/dist/esm/version.json +1 -1
  23. package/dist/types/services/use-plugins/index.d.ts +12 -0
  24. package/dist/types/ui/link-picker/index.d.ts +24 -13
  25. package/dist/types/ui/link-picker/link-search-list/index.d.ts +3 -5
  26. package/dist/types/ui/link-picker/styled.d.ts +1 -0
  27. package/dist/types/ui/link-picker/text-input/index.d.ts +1 -1
  28. package/package.json +4 -3
@@ -1,504 +1,331 @@
1
- import _defineProperty from "@babel/runtime/helpers/defineProperty";
1
+ import _extends from "@babel/runtime/helpers/extends";
2
2
 
3
3
  /** @jsx jsx */
4
- import { PureComponent } from 'react';
4
+ import { useCallback, useReducer } from 'react';
5
5
  import { jsx } from '@emotion/react';
6
- import { injectIntl } from 'react-intl-next';
6
+ import { useIntl, FormattedMessage } from 'react-intl-next';
7
+ import Button, { ButtonGroup } from '@atlaskit/button';
8
+ import { FormFooter } from '@atlaskit/form';
9
+ import EditorSearchIcon from '@atlaskit/icon/glyph/editor/search';
7
10
  import Tabs, { Tab, TabList } from '@atlaskit/tabs';
11
+ import VisuallyHidden from '@atlaskit/visually-hidden';
8
12
  import { messages } from './messages';
9
13
  import PanelTextInput from './text-input';
10
14
  import LinkSearchList from './link-search-list';
11
15
  import { normalizeUrl, isSafeUrl } from './url';
12
- import { rootContainerStyles, searchIconStyles, listTitleStyles } from './styled';
16
+ import { rootContainerStyles, searchIconStyles, listTitleStyles, tabsWrapperStyles } from './styled';
13
17
  import { browser } from './browser';
14
18
  import { transformTimeStamp } from './transformTimeStamp';
15
- import Announcer from './/announcer';
16
- import Button, { ButtonGroup } from '@atlaskit/button';
17
- import { FormFooter } from '@atlaskit/form';
18
- import EditorSearchIcon from '@atlaskit/icon/glyph/editor/search';
19
- import VisuallyHidden from '@atlaskit/visually-hidden';
19
+ import Announcer from './announcer';
20
20
  import LinkSearchNoResults from './link-search-no-results';
21
+ import { usePlugins } from '../../services/use-plugins';
21
22
  export const RECENT_SEARCH_LIST_SIZE = 5;
23
+ export const testIds = {
24
+ linkPicker: 'link-picker',
25
+ urlInputField: 'link-url',
26
+ textInputField: 'link-text',
27
+ searchIcon: 'link-picker-search-icon',
28
+ insertButton: 'link-picker-insert-button',
29
+ cancelButton: 'link-picker-cancel-button',
30
+ clearUrlButton: 'clear-text',
31
+ resultListTitle: 'link-picker-list-title',
32
+ emptyResultPage: 'link-search-no-results',
33
+ searchResultList: 'link-search-list',
34
+ searchResultItem: 'link-search-list-item',
35
+ searchResultLoadingIndicator: 'link-picker.results-loading-indicator',
36
+ urlError: 'link-error',
37
+ tabList: 'link-picker-tabs',
38
+ tabItem: 'link-picker-tab'
39
+ };
40
+ const initState = {
41
+ url: '',
42
+ displayText: '',
43
+ activeIndex: -1,
44
+ selectedIndex: -1,
45
+ invalidUrl: false,
46
+ activeTab: 0
47
+ };
48
+
49
+ function reducer(state, payload) {
50
+ if (payload.url && state.url !== payload.url) {
51
+ return { ...state,
52
+ invalidUrl: false,
53
+ selectedIndex: isSafeUrl(payload.url) && payload.url.length ? -1 : state.selectedIndex,
54
+ ...payload
55
+ };
56
+ }
22
57
 
23
- class LinkPicker extends PureComponent {
24
- constructor(props) {
25
- super(props);
26
-
27
- _defineProperty(this, "queryVersion", 0);
28
-
29
- _defineProperty(this, "updateInput", async input => {
30
- // If input is a valid URL, do not search or display link suggestions
31
- if (input.length && isSafeUrl(input)) {
32
- return this.setState({
33
- url: input,
34
- selectedIndex: -1,
35
- items: [],
36
- isLoading: false,
37
- invalidUrl: null
38
- });
39
- }
40
-
41
- return this.setState({
42
- url: input,
43
- // Unset selection on any input change
44
- selectedIndex: -1,
45
- invalidUrl: null
46
- }, () => {
47
- this.updateResults();
48
- });
49
- });
50
-
51
- _defineProperty(this, "createClearHandler", field => {
52
- return () => {
53
- switch (field) {
54
- case 'url':
55
- {
56
- this.updateInput('');
57
- break;
58
- }
59
-
60
- case 'displayText':
61
- {
62
- this.setState({
63
- [field]: ''
64
- });
65
- }
66
- }
67
- };
68
- });
69
-
70
- _defineProperty(this, "getScreenReaderText", () => {
71
- const {
72
- intl
73
- } = this.props;
74
- const {
75
- items,
76
- selectedIndex
77
- } = this.state;
78
-
79
- if (items.length && selectedIndex > -1) {
80
- const {
81
- name,
82
- container,
83
- lastUpdatedDate,
84
- lastViewedDate
85
- } = items[selectedIndex];
86
- const date = transformTimeStamp(intl, lastViewedDate, lastUpdatedDate);
87
- 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(' ');
88
- return [name, container, formattedDate].filter(Boolean).join(', ');
89
- }
90
- });
91
-
92
- _defineProperty(this, "getTabs", () => {
93
- if (this.props.plugins && this.props.plugins.length > 1) {
94
- return this.props.plugins.filter(plugin => !!plugin.tabTitle).map(plugin => ({
95
- tabTitle: plugin.tabTitle
96
- }));
97
- }
58
+ return { ...state,
59
+ ...payload
60
+ };
61
+ }
98
62
 
99
- return [];
63
+ function LinkPicker({
64
+ onSubmit,
65
+ onCancel,
66
+ plugins,
67
+ url: initUrl,
68
+ displayText: initDisplayText
69
+ }) {
70
+ const [state, dispatch] = useReducer(reducer, { ...initState,
71
+ url: normalizeUrl(initUrl) || '',
72
+ displayText: initDisplayText || ''
73
+ });
74
+ const {
75
+ activeIndex,
76
+ selectedIndex,
77
+ url,
78
+ displayText,
79
+ invalidUrl
80
+ } = state;
81
+ const isEditing = !!initUrl;
82
+ const intl = useIntl();
83
+ const {
84
+ items,
85
+ isLoading,
86
+ isSelectedItem,
87
+ isActivePlugin,
88
+ tabs
89
+ } = usePlugins(state, plugins);
90
+ const handleChangeUrl = useCallback(url => {
91
+ dispatch({
92
+ url
100
93
  });
101
-
102
- _defineProperty(this, "isUrlPopulatedWithSelectedItem", () => {
103
- /**
104
- * When we use ArrowKey to navigate through result items,
105
- * the URL field will be populated with the content of
106
- * selected item.
107
- * This function will check if the URL field is populated
108
- * with selected item.
109
- * It can be useful to detect whether we want to insert a
110
- * smartlink or a hyperlink with customized title
111
- */
112
- const {
113
- items,
114
- selectedIndex,
115
- url
116
- } = this.state;
117
- const selectedItem = items[selectedIndex];
118
-
119
- if (selectedItem && selectedItem.url === url) {
120
- return true;
121
- }
122
-
123
- return false;
94
+ }, []);
95
+ const handleChangeText = useCallback(displayText => {
96
+ dispatch({
97
+ displayText
124
98
  });
99
+ }, []);
100
+ const handleKeyDown = useCallback(event => {
101
+ const {
102
+ keyCode
103
+ } = event;
104
+ const KEY_CODE_ARROW_DOWN = 40;
105
+ const KEY_CODE_ARROW_UP = 38;
125
106
 
126
- _defineProperty(this, "handleSelected", objectId => {
127
- const {
128
- items
129
- } = this.state;
130
- const selectedItem = items.find(item => item.objectId === objectId);
107
+ if (!items || !items.length) {
108
+ return;
109
+ }
131
110
 
132
- if (selectedItem) {
133
- const {
134
- url,
135
- name
136
- } = selectedItem;
137
- this.handleInsert(url, name, 'typeAhead');
138
- }
139
- });
111
+ let updatedIndex = activeIndex;
140
112
 
141
- _defineProperty(this, "handleInsert", (url, title, inputType) => {
142
- const {
143
- onSubmit
144
- } = this.props;
145
- const {
146
- url: rawUrl,
147
- displayText
148
- } = this.state;
113
+ switch (keyCode) {
114
+ case KEY_CODE_ARROW_DOWN:
115
+ // down
116
+ event.preventDefault();
117
+ updatedIndex = (activeIndex + 1) % items.length;
118
+ break;
149
119
 
150
- if (onSubmit) {
151
- onSubmit({
152
- url,
153
- displayText: displayText || null,
154
- title: title || null,
155
- meta: {
156
- inputMethod: inputType
157
- },
158
- ...(inputType === 'manual' ? {
159
- rawUrl
160
- } : {})
161
- });
162
- }
163
- });
120
+ case KEY_CODE_ARROW_UP:
121
+ // up
122
+ event.preventDefault();
123
+ updatedIndex = activeIndex > 0 ? activeIndex - 1 : items.length - 1;
124
+ break;
125
+ }
164
126
 
165
- _defineProperty(this, "handleMouseEnterResultItem", objectId => {
166
- const {
167
- items
168
- } = this.state;
169
- const index = findIndex(items, item => item.objectId === objectId);
170
- this.setState({
171
- activeIndex: index
127
+ if ([KEY_CODE_ARROW_DOWN, KEY_CODE_ARROW_UP].includes(keyCode) && items[updatedIndex]) {
128
+ dispatch({
129
+ activeIndex: updatedIndex,
130
+ selectedIndex: updatedIndex,
131
+ url: items[updatedIndex].url,
132
+ invalidUrl: false
172
133
  });
134
+ }
135
+ }, [activeIndex, items]);
136
+ const handleClear = useCallback(field => {
137
+ dispatch({
138
+ [field]: ''
173
139
  });
174
-
175
- _defineProperty(this, "handleMouseLeaveResultItem", objectId => {
176
- const {
177
- items,
178
- activeIndex
179
- } = this.state;
180
- const index = findIndex(items, item => item.objectId === objectId); // This is to avoid updating index that was set by other mouseenter event
181
-
182
- if (activeIndex === index) {
183
- this.setState({
184
- activeIndex: -1
185
- });
186
- }
140
+ }, []);
141
+ const handleMouseEnterResultItem = useCallback(objectId => {
142
+ const index = findIndex(items, item => item.objectId === objectId);
143
+ dispatch({
144
+ activeIndex: index
187
145
  });
146
+ }, [items]);
147
+ const handleMouseLeaveResultItem = useCallback(objectId => {
148
+ const index = findIndex(items, item => item.objectId === objectId); // This is to avoid updating index that was set by other mouseenter event
188
149
 
189
- _defineProperty(this, "handleSubmit", () => {
190
- const {
191
- url,
192
- selectedIndex,
193
- items
194
- } = this.state;
195
- const {
196
- intl
197
- } = this.props;
198
- const selectedItem = items[selectedIndex];
199
-
200
- if (this.isUrlPopulatedWithSelectedItem()) {
201
- return this.handleInsert(selectedItem.url, selectedItem.name, 'typeAhead');
202
- }
203
-
204
- const normalized = normalizeUrl(url);
205
-
206
- if (normalized) {
207
- return this.handleInsert(normalized, null, 'manual');
208
- }
209
-
210
- return this.setState({
211
- invalidUrl: intl.formatMessage(messages.linkInvalid)
150
+ if (activeIndex === index) {
151
+ dispatch({
152
+ activeIndex: -1
212
153
  });
154
+ }
155
+ }, [activeIndex, items]);
156
+ const handleInsert = useCallback((url, title, inputType) => {
157
+ onSubmit({
158
+ url,
159
+ displayText: displayText || null,
160
+ title: title || null,
161
+ meta: {
162
+ inputMethod: inputType
163
+ },
164
+ ...(inputType === 'manual' ? {
165
+ rawUrl: state.url
166
+ } : {})
213
167
  });
168
+ }, [displayText, onSubmit, state.url]);
169
+ const handleSelected = useCallback(objectId => {
170
+ const selectedItem = items.find(item => item.objectId === objectId);
214
171
 
215
- _defineProperty(this, "handleKeyDown", event => {
216
- const {
217
- items,
218
- activeIndex
219
- } = this.state;
220
- const {
221
- keyCode
222
- } = event;
223
- const KEY_CODE_ARROW_DOWN = 40;
224
- const KEY_CODE_ARROW_UP = 38;
225
-
226
- if (!items || !items.length) {
227
- return;
228
- }
229
-
230
- let updatedIndex = activeIndex;
231
-
232
- switch (keyCode) {
233
- case KEY_CODE_ARROW_DOWN:
234
- // down
235
- event.preventDefault();
236
- updatedIndex = (activeIndex + 1) % items.length;
237
- break;
238
-
239
- case KEY_CODE_ARROW_UP:
240
- // up
241
- event.preventDefault();
242
- updatedIndex = activeIndex > 0 ? activeIndex - 1 : items.length - 1;
243
- break;
244
- }
245
-
246
- if ([KEY_CODE_ARROW_DOWN, KEY_CODE_ARROW_UP].includes(keyCode) && items[updatedIndex]) {
247
- this.setState({
248
- activeIndex: updatedIndex,
249
- selectedIndex: updatedIndex,
250
- url: items[updatedIndex].url,
251
- invalidUrl: null
252
- });
253
- }
254
- });
255
-
256
- _defineProperty(this, "handleCancel", () => {
172
+ if (selectedItem) {
257
173
  const {
258
- onCancel
259
- } = this.props;
260
-
261
- if (onCancel) {
262
- onCancel();
263
- }
264
- });
265
-
266
- _defineProperty(this, "updateTextInput", displayText => {
267
- this.setState({
268
- displayText: displayText
269
- });
270
- });
271
-
272
- this.state = {
273
- selectedIndex: -1,
274
- activeIndex: -1,
275
- isLoading: false,
276
- isEditing: !!props.url,
277
- url: normalizeUrl(props.url) || '',
278
- displayText: props.displayText || '',
279
- items: [],
280
- invalidUrl: null,
281
- activePlugin: 0
282
- };
283
- /* Cache functions */
284
-
285
- this.handleClearUrl = this.createClearHandler('url');
286
- this.handleClearDisplayText = this.createClearHandler('displayText');
287
- }
288
-
289
- componentDidMount() {
290
- this.loadInitialLinkSearchResult();
291
- }
292
-
293
- loadInitialLinkSearchResult() {
294
- const {
295
- url
296
- } = this.state;
297
-
298
- if (!url) {
299
- this.updateResults();
174
+ url,
175
+ name
176
+ } = selectedItem;
177
+ handleInsert(url, name, 'typeAhead');
300
178
  }
301
- }
179
+ }, [handleInsert, items]);
180
+ const handleSubmit = useCallback(() => {
181
+ const selectedItem = items[selectedIndex];
302
182
 
303
- getActivePlugin() {
304
- if (!this.props.plugins || this.props.plugins.length === 0) {
305
- return null;
183
+ if (isSelectedItem) {
184
+ return handleInsert(selectedItem.url, selectedItem.name, 'typeAhead');
306
185
  }
307
186
 
308
- return this.props.plugins[this.state.activePlugin];
309
- }
310
- /**
311
- * Interface between link picker and plugins to update results
312
- */
313
-
187
+ const normalized = normalizeUrl(url);
314
188
 
315
- async updateResults() {
316
- const queryVersion = ++this.queryVersion;
317
- const activePlugin = this.getActivePlugin();
318
-
319
- if (!activePlugin) {
320
- return;
189
+ if (normalized) {
190
+ return handleInsert(normalized, null, 'manual');
321
191
  }
322
192
 
323
- const updates = activePlugin.resolve({
324
- query: this.state.url
193
+ return dispatch({
194
+ invalidUrl: true
325
195
  });
326
- this.setState({
327
- isLoading: true,
328
- items: []
329
- });
330
-
331
- try {
332
- while (queryVersion === this.queryVersion) {
333
- let done = false;
334
- let value;
335
-
336
- if (updates instanceof Promise) {
337
- done = true;
338
- value = await updates;
339
- } else {
340
- var _result$done;
341
-
342
- const result = await updates.next();
343
- done = (_result$done = result.done) !== null && _result$done !== void 0 ? _result$done : false;
344
- value = result.value;
345
- }
346
-
347
- if (queryVersion !== this.queryVersion) {
348
- return;
349
- }
196
+ }, [handleInsert, isSelectedItem, items, selectedIndex, url]);
197
+ const linkPlaceHolder = isActivePlugin ? messages.placeholder : messages.linkPlaceholder;
198
+ const linkListTitle = url ? messages.titleResults : messages.titleRecentlyViewed;
199
+ const insertButtonMsg = isEditing ? messages.saveButton : messages.insertButton;
200
+ const noResults = isActivePlugin && !isLoading && !items.length && !isSafeUrl(url);
201
+ const screenReaderDescriptionId = 'search-recent-links-field-description';
202
+ const linkSearchListId = 'link-picker-search-list';
203
+ const ariaActiveDescendant = selectedIndex > -1 ? `link-search-list-item-${selectedIndex}` : ''; // Added workaround with a screen reader Announcer specifically for VoiceOver + Safari
204
+ // as the Aria design pattern for combobox does not work in this case
205
+ // for details: https://a11y-internal.atlassian.net/browse/AK-740
206
+
207
+ const screenReaderText = browser.safari && getScreenReaderText(items, selectedIndex, intl);
208
+ const searchIcon = isActivePlugin && jsx("span", {
209
+ css: searchIconStyles,
210
+ "data-testid": testIds.searchIcon
211
+ }, jsx(EditorSearchIcon, {
212
+ size: "medium",
213
+ label: ''
214
+ }));
215
+ return jsx("div", {
216
+ "data-testid": testIds.linkPicker,
217
+ css: rootContainerStyles
218
+ }, screenReaderText && jsx(Announcer, {
219
+ ariaLive: "assertive",
220
+ text: screenReaderText,
221
+ ariaRelevant: "additions",
222
+ delay: 250
223
+ }), jsx(VisuallyHidden, {
224
+ id: screenReaderDescriptionId
225
+ }, jsx(FormattedMessage, messages.searchLinkAriaDescription)), jsx(PanelTextInput, {
226
+ role: "combobox",
227
+ autoComplete: "off",
228
+ name: "url",
229
+ testId: testIds.urlInputField,
230
+ label: intl.formatMessage(messages.linkLabel),
231
+ placeholder: intl.formatMessage(linkPlaceHolder),
232
+ value: url,
233
+ autoFocus: true,
234
+ elemBeforeInput: searchIcon,
235
+ clearLabel: intl.formatMessage(messages.clearLink),
236
+ "aria-expanded": true,
237
+ "aria-autocomplete": "list",
238
+ "aria-controls": linkSearchListId,
239
+ "aria-activedescendant": ariaActiveDescendant,
240
+ "aria-describedby": screenReaderDescriptionId,
241
+ error: invalidUrl ? intl.formatMessage(messages.linkInvalid) : null,
242
+ onClear: handleClear,
243
+ onSubmit: handleSubmit,
244
+ onKeyDown: handleKeyDown,
245
+ onChange: handleChangeUrl
246
+ }), jsx(PanelTextInput, {
247
+ autoComplete: "off",
248
+ name: "displayText",
249
+ testId: testIds.textInputField,
250
+ value: displayText,
251
+ label: intl.formatMessage(messages.linkTextLabel),
252
+ placeholder: intl.formatMessage(messages.linkTextPlaceholder),
253
+ clearLabel: intl.formatMessage(messages.clearText),
254
+ "aria-label": intl.formatMessage(messages.linkAriaLabel),
255
+ onClear: handleClear,
256
+ onSubmit: handleSubmit,
257
+ onKeyDown: handleKeyDown,
258
+ onChange: handleChangeText
259
+ }), tabs.length > 0 && jsx("div", {
260
+ css: tabsWrapperStyles
261
+ }, jsx(Tabs, {
262
+ id: testIds.tabList,
263
+ testId: testIds.tabList,
264
+ onChange: activeTab => dispatch({
265
+ selectedIndex: -1,
266
+ activeIndex: -1,
267
+ invalidUrl: false,
268
+ activeTab
269
+ })
270
+ }, jsx(TabList, null, tabs.map(tab => jsx(Tab, {
271
+ key: tab.tabTitle,
272
+ testId: testIds.tabItem
273
+ }, tab.tabTitle))))), !!items.length && jsx("div", {
274
+ css: listTitleStyles,
275
+ id: testIds.resultListTitle,
276
+ "data-testid": testIds.resultListTitle
277
+ }, jsx(FormattedMessage, linkListTitle)), jsx(VisuallyHidden, {
278
+ id: "fabric.smartcard.linkpicker.suggested.results"
279
+ }, url && jsx(FormattedMessage, _extends({}, messages.searchLinkResults, {
280
+ values: {
281
+ count: items.length
282
+ },
283
+ "aria-live": "polite",
284
+ "aria-atomic": "true"
285
+ }))), jsx(LinkSearchList, {
286
+ ariaLabelledBy: testIds.resultListTitle,
287
+ ariaControls: "fabric.smartcard.linkpicker.suggested.results",
288
+ id: linkSearchListId,
289
+ role: "listbox",
290
+ items: items,
291
+ isLoading: isLoading,
292
+ selectedIndex: selectedIndex,
293
+ activeIndex: activeIndex,
294
+ onSelect: handleSelected,
295
+ onMouseEnter: handleMouseEnterResultItem,
296
+ onMouseLeave: handleMouseLeaveResultItem
297
+ }), noResults && jsx(LinkSearchNoResults, {
298
+ testId: testIds.emptyResultPage,
299
+ header: intl.formatMessage(messages.noResults),
300
+ description: intl.formatMessage(messages.noResultsDescription)
301
+ }), jsx(FormFooter, {
302
+ align: "end"
303
+ }, jsx(ButtonGroup, null, jsx(Button, {
304
+ appearance: "default",
305
+ onClick: onCancel,
306
+ testId: testIds.cancelButton
307
+ }, intl.formatMessage(messages.cancelButton)), jsx(Button, {
308
+ type: "submit",
309
+ appearance: "primary",
310
+ onClick: handleSubmit,
311
+ testId: testIds.insertButton
312
+ }, intl.formatMessage(insertButtonMsg)))));
313
+ }
350
314
 
351
- this.setState(prev => ({
352
- items: limit(value.data),
353
- isLoading: !done
354
- }));
315
+ export default LinkPicker;
355
316
 
356
- if (done) {
357
- return;
358
- }
359
- }
360
- } catch {
361
- this.setState({
362
- isLoading: false
363
- });
364
- }
365
- }
366
-
367
- render() {
317
+ function getScreenReaderText(items, selectedIndex, intl) {
318
+ if (items.length && selectedIndex > -1) {
368
319
  const {
369
- items,
370
- isLoading,
371
- isEditing,
372
- activeIndex,
373
- selectedIndex,
374
- url,
375
- displayText
376
- } = this.state;
377
- const {
378
- intl: {
379
- formatMessage
380
- }
381
- } = this.props;
382
- const activePlugin = this.getActivePlugin();
383
- const tabs = this.getTabs();
384
- const linkPlaceHolder = formatMessage(activePlugin ? messages.placeholder : messages.linkPlaceholder);
385
- const linkLabel = formatMessage(messages.linkLabel);
386
- const linkTextLabel = formatMessage(messages.linkTextLabel);
387
- const linkTextPlaceHolder = formatMessage(messages.linkTextPlaceholder);
388
- const screenReaderDescriptionId = 'search-recent-links-field-description';
389
- const linkSearchListId = 'link-picker-search-list';
390
- const ariaActiveDescendant = selectedIndex > -1 ? `link-search-list-item-${selectedIndex}` : ''; // Added workaround with a screen reader Announcer specifically for VoiceOver + Safari
391
- // as the Aria design pattern for combobox does not work in this case
392
- // for details: https://a11y-internal.atlassian.net/browse/AK-740
393
-
394
- const screenReaderText = browser.safari && this.getScreenReaderText();
395
- const searchIcon = activePlugin && jsx("span", {
396
- css: searchIconStyles,
397
- "data-testid": "link-picker-search-icon"
398
- }, jsx(EditorSearchIcon, {
399
- size: "medium",
400
- label: ''
401
- }));
402
- const noResults = activePlugin && !isLoading && !items.length && !isSafeUrl(url);
403
- return jsx("div", {
404
- "data-testid": "link-picker",
405
- css: rootContainerStyles
406
- }, tabs.length > 0 && jsx(Tabs, {
407
- id: "link-picker-tabs",
408
- testId: "link-picker-tabs",
409
- onChange: activePlugin => this.setState({
410
- activePlugin
411
- }, () => {
412
- this.updateResults();
413
- })
414
- }, jsx(TabList, null, tabs.map(tab => {
415
- const label = tab.tabTitle;
416
- return jsx(Tab, {
417
- key: label,
418
- testId: "link-picker-tab"
419
- }, label);
420
- }))), screenReaderText && jsx(Announcer, {
421
- ariaLive: "assertive",
422
- text: screenReaderText,
423
- ariaRelevant: "additions",
424
- delay: 250
425
- }), jsx(VisuallyHidden, null, jsx("span", {
426
- "aria-hidden": "true",
427
- id: screenReaderDescriptionId
428
- }, formatMessage(messages.searchLinkAriaDescription))), jsx(PanelTextInput, {
429
- name: "link",
430
- testId: "link-url",
431
- role: "combobox",
432
- label: linkLabel,
433
- placeholder: linkPlaceHolder,
434
- value: url,
435
- autoFocus: true,
436
- autoComplete: "off",
437
- elemBeforeInput: searchIcon,
438
- onSubmit: this.handleSubmit,
439
- onChange: this.updateInput,
440
- onKeyDown: this.handleKeyDown,
441
- onClear: this.handleClearUrl,
442
- error: this.state.invalidUrl,
443
- clearLabel: formatMessage(messages.clearLink),
444
- "aria-controls": linkSearchListId,
445
- "aria-autocomplete": "list",
446
- "aria-activedescendant": ariaActiveDescendant,
447
- "aria-describedby": screenReaderDescriptionId,
448
- "aria-expanded": true
449
- }), jsx(PanelTextInput, {
450
- name: "link-text",
451
- testId: "link-text",
452
- autoComplete: "off",
453
- label: linkTextLabel,
454
- placeholder: linkTextPlaceHolder,
455
- "aria-label": formatMessage(messages.linkAriaLabel),
456
- value: displayText,
457
- onSubmit: this.handleSubmit,
458
- onKeyDown: this.handleKeyDown,
459
- onChange: this.updateTextInput,
460
- onClear: this.handleClearDisplayText,
461
- clearLabel: formatMessage(messages.clearText)
462
- }), !!items.length && jsx("div", {
463
- css: listTitleStyles,
464
- id: "link-picker-list-title",
465
- "data-testid": "link-picker-list-title"
466
- }, formatMessage(this.state.url ? messages.titleResults : messages.titleRecentlyViewed)), jsx(VisuallyHidden, null, jsx("span", {
467
- "aria-live": "polite",
468
- "aria-atomic": "true",
469
- id: "fabric.smartcard.linkpicker.suggested.results"
470
- }, url && formatMessage(messages.searchLinkResults, {
471
- count: items.length
472
- }))), jsx(LinkSearchList, {
473
- ariaLabelledBy: "link-picker-list-title",
474
- ariaControls: "fabric.smartcard.linkpicker.suggested.results",
475
- id: linkSearchListId,
476
- role: "listbox",
477
- items: items,
478
- isLoading: isLoading,
479
- selectedIndex: selectedIndex,
480
- activeIndex: activeIndex,
481
- onSelect: this.handleSelected,
482
- onMouseEnter: this.handleMouseEnterResultItem,
483
- onMouseLeave: this.handleMouseLeaveResultItem
484
- }), noResults && jsx(LinkSearchNoResults, {
485
- testId: "link-search-no-results",
486
- header: formatMessage(messages.noResults),
487
- description: formatMessage(messages.noResultsDescription)
488
- }), jsx(FormFooter, {
489
- align: "end"
490
- }, jsx(ButtonGroup, null, jsx(Button, {
491
- appearance: "default",
492
- onClick: this.handleCancel,
493
- testId: "link-picker-cancel-button"
494
- }, formatMessage(messages.cancelButton)), jsx(Button, {
495
- type: "submit",
496
- appearance: "primary",
497
- onClick: this.handleSubmit,
498
- testId: "link-picker-insert-button"
499
- }, formatMessage(isEditing ? messages.saveButton : messages.insertButton)))));
320
+ name,
321
+ container,
322
+ lastUpdatedDate,
323
+ lastViewedDate
324
+ } = items[selectedIndex];
325
+ const date = transformTimeStamp(intl, lastViewedDate, lastUpdatedDate);
326
+ 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(' ');
327
+ return [name, container, formattedDate].filter(Boolean).join(', ');
500
328
  }
501
-
502
329
  }
503
330
 
504
331
  function findIndex(array, predicate) {
@@ -512,10 +339,4 @@ function findIndex(array, predicate) {
512
339
  return false;
513
340
  });
514
341
  return index;
515
- }
516
-
517
- function limit(items) {
518
- return items.slice(0, RECENT_SEARCH_LIST_SIZE);
519
- }
520
-
521
- export default injectIntl(LinkPicker);
342
+ }