@atlaskit/link-picker 1.4.1 → 1.6.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 (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 +314 -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 +294 -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 +304 -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 +26 -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 +5 -3
@@ -1,504 +1,337 @@
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, useLayoutEffect, 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
+ onContentResize,
67
+ plugins,
68
+ url: initUrl,
69
+ displayText: initDisplayText
70
+ }) {
71
+ const [state, dispatch] = useReducer(reducer, { ...initState,
72
+ url: normalizeUrl(initUrl) || '',
73
+ displayText: initDisplayText || ''
74
+ });
75
+ const {
76
+ activeIndex,
77
+ selectedIndex,
78
+ url,
79
+ displayText,
80
+ invalidUrl
81
+ } = state;
82
+ const isEditing = !!initUrl;
83
+ const intl = useIntl();
84
+ const {
85
+ items,
86
+ isLoading,
87
+ isSelectedItem,
88
+ isActivePlugin,
89
+ tabs
90
+ } = usePlugins(state, plugins);
91
+ useLayoutEffect(() => {
92
+ if (onContentResize) {
93
+ onContentResize();
94
+ }
95
+ }, [onContentResize, items, isLoading, isActivePlugin, tabs]);
96
+ const handleChangeUrl = useCallback(url => {
97
+ dispatch({
98
+ url
100
99
  });
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;
100
+ }, []);
101
+ const handleChangeText = useCallback(displayText => {
102
+ dispatch({
103
+ displayText
124
104
  });
105
+ }, []);
106
+ const handleKeyDown = useCallback(event => {
107
+ const {
108
+ keyCode
109
+ } = event;
110
+ const KEY_CODE_ARROW_DOWN = 40;
111
+ const KEY_CODE_ARROW_UP = 38;
125
112
 
126
- _defineProperty(this, "handleSelected", objectId => {
127
- const {
128
- items
129
- } = this.state;
130
- const selectedItem = items.find(item => item.objectId === objectId);
113
+ if (!items || !items.length) {
114
+ return;
115
+ }
131
116
 
132
- if (selectedItem) {
133
- const {
134
- url,
135
- name
136
- } = selectedItem;
137
- this.handleInsert(url, name, 'typeAhead');
138
- }
139
- });
117
+ let updatedIndex = activeIndex;
140
118
 
141
- _defineProperty(this, "handleInsert", (url, title, inputType) => {
142
- const {
143
- onSubmit
144
- } = this.props;
145
- const {
146
- url: rawUrl,
147
- displayText
148
- } = this.state;
119
+ switch (keyCode) {
120
+ case KEY_CODE_ARROW_DOWN:
121
+ // down
122
+ event.preventDefault();
123
+ updatedIndex = (activeIndex + 1) % items.length;
124
+ break;
149
125
 
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
- });
126
+ case KEY_CODE_ARROW_UP:
127
+ // up
128
+ event.preventDefault();
129
+ updatedIndex = activeIndex > 0 ? activeIndex - 1 : items.length - 1;
130
+ break;
131
+ }
164
132
 
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
133
+ if ([KEY_CODE_ARROW_DOWN, KEY_CODE_ARROW_UP].includes(keyCode) && items[updatedIndex]) {
134
+ dispatch({
135
+ activeIndex: updatedIndex,
136
+ selectedIndex: updatedIndex,
137
+ url: items[updatedIndex].url,
138
+ invalidUrl: false
172
139
  });
140
+ }
141
+ }, [activeIndex, items]);
142
+ const handleClear = useCallback(field => {
143
+ dispatch({
144
+ [field]: ''
173
145
  });
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
- }
146
+ }, []);
147
+ const handleMouseEnterResultItem = useCallback(objectId => {
148
+ const index = findIndex(items, item => item.objectId === objectId);
149
+ dispatch({
150
+ activeIndex: index
187
151
  });
152
+ }, [items]);
153
+ 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
188
155
 
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)
156
+ if (activeIndex === index) {
157
+ dispatch({
158
+ activeIndex: -1
212
159
  });
160
+ }
161
+ }, [activeIndex, items]);
162
+ const handleInsert = useCallback((url, title, inputType) => {
163
+ onSubmit({
164
+ url,
165
+ displayText: displayText || null,
166
+ title: title || null,
167
+ meta: {
168
+ inputMethod: inputType
169
+ },
170
+ ...(inputType === 'manual' ? {
171
+ rawUrl: state.url
172
+ } : {})
213
173
  });
174
+ }, [displayText, onSubmit, state.url]);
175
+ const handleSelected = useCallback(objectId => {
176
+ const selectedItem = items.find(item => item.objectId === objectId);
214
177
 
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", () => {
178
+ if (selectedItem) {
257
179
  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();
180
+ url,
181
+ name
182
+ } = selectedItem;
183
+ handleInsert(url, name, 'typeAhead');
300
184
  }
301
- }
185
+ }, [handleInsert, items]);
186
+ const handleSubmit = useCallback(() => {
187
+ const selectedItem = items[selectedIndex];
302
188
 
303
- getActivePlugin() {
304
- if (!this.props.plugins || this.props.plugins.length === 0) {
305
- return null;
189
+ if (isSelectedItem) {
190
+ return handleInsert(selectedItem.url, selectedItem.name, 'typeAhead');
306
191
  }
307
192
 
308
- return this.props.plugins[this.state.activePlugin];
309
- }
310
- /**
311
- * Interface between link picker and plugins to update results
312
- */
313
-
193
+ const normalized = normalizeUrl(url);
314
194
 
315
- async updateResults() {
316
- const queryVersion = ++this.queryVersion;
317
- const activePlugin = this.getActivePlugin();
318
-
319
- if (!activePlugin) {
320
- return;
195
+ if (normalized) {
196
+ return handleInsert(normalized, null, 'manual');
321
197
  }
322
198
 
323
- const updates = activePlugin.resolve({
324
- query: this.state.url
199
+ return dispatch({
200
+ invalidUrl: true
325
201
  });
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
- }
202
+ }, [handleInsert, isSelectedItem, items, selectedIndex, url]);
203
+ const linkPlaceHolder = isActivePlugin ? messages.placeholder : messages.linkPlaceholder;
204
+ const linkListTitle = url ? messages.titleResults : messages.titleRecentlyViewed;
205
+ const insertButtonMsg = isEditing ? messages.saveButton : messages.insertButton;
206
+ const noResults = isActivePlugin && !isLoading && !items.length && !isSafeUrl(url);
207
+ const screenReaderDescriptionId = 'search-recent-links-field-description';
208
+ const linkSearchListId = 'link-picker-search-list';
209
+ const ariaActiveDescendant = selectedIndex > -1 ? `link-search-list-item-${selectedIndex}` : ''; // Added workaround with a screen reader Announcer specifically for VoiceOver + Safari
210
+ // as the Aria design pattern for combobox does not work in this case
211
+ // for details: https://a11y-internal.atlassian.net/browse/AK-740
212
+
213
+ const screenReaderText = browser.safari && getScreenReaderText(items, selectedIndex, intl);
214
+ const searchIcon = isActivePlugin && jsx("span", {
215
+ css: searchIconStyles,
216
+ "data-testid": testIds.searchIcon
217
+ }, jsx(EditorSearchIcon, {
218
+ size: "medium",
219
+ label: ''
220
+ }));
221
+ return jsx("div", {
222
+ "data-testid": testIds.linkPicker,
223
+ css: rootContainerStyles
224
+ }, screenReaderText && jsx(Announcer, {
225
+ ariaLive: "assertive",
226
+ text: screenReaderText,
227
+ ariaRelevant: "additions",
228
+ delay: 250
229
+ }), jsx(VisuallyHidden, {
230
+ id: screenReaderDescriptionId
231
+ }, jsx(FormattedMessage, messages.searchLinkAriaDescription)), jsx(PanelTextInput, {
232
+ role: "combobox",
233
+ autoComplete: "off",
234
+ name: "url",
235
+ testId: testIds.urlInputField,
236
+ label: intl.formatMessage(messages.linkLabel),
237
+ placeholder: intl.formatMessage(linkPlaceHolder),
238
+ value: url,
239
+ autoFocus: true,
240
+ elemBeforeInput: searchIcon,
241
+ clearLabel: intl.formatMessage(messages.clearLink),
242
+ "aria-expanded": true,
243
+ "aria-autocomplete": "list",
244
+ "aria-controls": linkSearchListId,
245
+ "aria-activedescendant": ariaActiveDescendant,
246
+ "aria-describedby": screenReaderDescriptionId,
247
+ error: invalidUrl ? intl.formatMessage(messages.linkInvalid) : null,
248
+ onClear: handleClear,
249
+ onSubmit: handleSubmit,
250
+ onKeyDown: handleKeyDown,
251
+ onChange: handleChangeUrl
252
+ }), jsx(PanelTextInput, {
253
+ autoComplete: "off",
254
+ name: "displayText",
255
+ testId: testIds.textInputField,
256
+ value: displayText,
257
+ label: intl.formatMessage(messages.linkTextLabel),
258
+ placeholder: intl.formatMessage(messages.linkTextPlaceholder),
259
+ clearLabel: intl.formatMessage(messages.clearText),
260
+ "aria-label": intl.formatMessage(messages.linkAriaLabel),
261
+ onClear: handleClear,
262
+ onSubmit: handleSubmit,
263
+ onKeyDown: handleKeyDown,
264
+ onChange: handleChangeText
265
+ }), tabs.length > 0 && jsx("div", {
266
+ css: tabsWrapperStyles
267
+ }, jsx(Tabs, {
268
+ id: testIds.tabList,
269
+ testId: testIds.tabList,
270
+ onChange: activeTab => dispatch({
271
+ selectedIndex: -1,
272
+ activeIndex: -1,
273
+ invalidUrl: false,
274
+ activeTab
275
+ })
276
+ }, jsx(TabList, null, tabs.map(tab => jsx(Tab, {
277
+ key: tab.tabTitle,
278
+ testId: testIds.tabItem
279
+ }, tab.tabTitle))))), !!items.length && jsx("div", {
280
+ css: listTitleStyles,
281
+ id: testIds.resultListTitle,
282
+ "data-testid": testIds.resultListTitle
283
+ }, jsx(FormattedMessage, linkListTitle)), jsx(VisuallyHidden, {
284
+ id: "fabric.smartcard.linkpicker.suggested.results"
285
+ }, url && jsx(FormattedMessage, _extends({}, messages.searchLinkResults, {
286
+ values: {
287
+ count: items.length
288
+ },
289
+ "aria-live": "polite",
290
+ "aria-atomic": "true"
291
+ }))), jsx(LinkSearchList, {
292
+ ariaLabelledBy: testIds.resultListTitle,
293
+ ariaControls: "fabric.smartcard.linkpicker.suggested.results",
294
+ id: linkSearchListId,
295
+ role: "listbox",
296
+ items: items,
297
+ isLoading: isLoading,
298
+ selectedIndex: selectedIndex,
299
+ activeIndex: activeIndex,
300
+ onSelect: handleSelected,
301
+ onMouseEnter: handleMouseEnterResultItem,
302
+ onMouseLeave: handleMouseLeaveResultItem
303
+ }), noResults && jsx(LinkSearchNoResults, {
304
+ testId: testIds.emptyResultPage,
305
+ header: intl.formatMessage(messages.noResults),
306
+ description: intl.formatMessage(messages.noResultsDescription)
307
+ }), jsx(FormFooter, {
308
+ align: "end"
309
+ }, jsx(ButtonGroup, null, jsx(Button, {
310
+ appearance: "default",
311
+ onClick: onCancel,
312
+ testId: testIds.cancelButton
313
+ }, intl.formatMessage(messages.cancelButton)), jsx(Button, {
314
+ type: "submit",
315
+ appearance: "primary",
316
+ onClick: handleSubmit,
317
+ testId: testIds.insertButton
318
+ }, intl.formatMessage(insertButtonMsg)))));
319
+ }
350
320
 
351
- this.setState(prev => ({
352
- items: limit(value.data),
353
- isLoading: !done
354
- }));
321
+ export default LinkPicker;
355
322
 
356
- if (done) {
357
- return;
358
- }
359
- }
360
- } catch {
361
- this.setState({
362
- isLoading: false
363
- });
364
- }
365
- }
366
-
367
- render() {
323
+ function getScreenReaderText(items, selectedIndex, intl) {
324
+ if (items.length && selectedIndex > -1) {
368
325
  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
- }, screenReaderText && jsx(Announcer, {
407
- ariaLive: "assertive",
408
- text: screenReaderText,
409
- ariaRelevant: "additions",
410
- delay: 250
411
- }), jsx(VisuallyHidden, null, jsx("span", {
412
- "aria-hidden": "true",
413
- id: screenReaderDescriptionId
414
- }, formatMessage(messages.searchLinkAriaDescription))), jsx(PanelTextInput, {
415
- name: "link",
416
- testId: "link-url",
417
- role: "combobox",
418
- label: linkLabel,
419
- placeholder: linkPlaceHolder,
420
- value: url,
421
- autoFocus: true,
422
- autoComplete: "off",
423
- elemBeforeInput: searchIcon,
424
- onSubmit: this.handleSubmit,
425
- onChange: this.updateInput,
426
- onKeyDown: this.handleKeyDown,
427
- onClear: this.handleClearUrl,
428
- error: this.state.invalidUrl,
429
- clearLabel: formatMessage(messages.clearLink),
430
- "aria-controls": linkSearchListId,
431
- "aria-autocomplete": "list",
432
- "aria-activedescendant": ariaActiveDescendant,
433
- "aria-describedby": screenReaderDescriptionId,
434
- "aria-expanded": true
435
- }), jsx(PanelTextInput, {
436
- name: "link-text",
437
- testId: "link-text",
438
- autoComplete: "off",
439
- label: linkTextLabel,
440
- placeholder: linkTextPlaceHolder,
441
- "aria-label": formatMessage(messages.linkAriaLabel),
442
- value: displayText,
443
- onSubmit: this.handleSubmit,
444
- onKeyDown: this.handleKeyDown,
445
- onChange: this.updateTextInput,
446
- onClear: this.handleClearDisplayText,
447
- clearLabel: formatMessage(messages.clearText)
448
- }), tabs.length > 0 && jsx(Tabs, {
449
- id: "link-picker-tabs",
450
- testId: "link-picker-tabs",
451
- onChange: activePlugin => this.setState({
452
- activePlugin
453
- }, () => {
454
- this.updateResults();
455
- })
456
- }, jsx(TabList, null, tabs.map(tab => {
457
- const label = tab.tabTitle;
458
- return jsx(Tab, {
459
- key: label,
460
- testId: "link-picker-tab"
461
- }, label);
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)))));
326
+ name,
327
+ container,
328
+ lastUpdatedDate,
329
+ lastViewedDate
330
+ } = items[selectedIndex];
331
+ const date = transformTimeStamp(intl, lastViewedDate, lastUpdatedDate);
332
+ 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
+ return [name, container, formattedDate].filter(Boolean).join(', ');
500
334
  }
501
-
502
335
  }
503
336
 
504
337
  function findIndex(array, predicate) {
@@ -512,10 +345,4 @@ function findIndex(array, predicate) {
512
345
  return false;
513
346
  });
514
347
  return index;
515
- }
516
-
517
- function limit(items) {
518
- return items.slice(0, RECENT_SEARCH_LIST_SIZE);
519
- }
520
-
521
- export default injectIntl(LinkPicker);
348
+ }