@atlaskit/link-picker 1.1.1 → 1.1.2

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 (80) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/cjs/browser.js +39 -0
  3. package/dist/cjs/index.js +13 -0
  4. package/dist/cjs/messages.js +112 -0
  5. package/dist/cjs/transformTimeStamp.js +55 -0
  6. package/dist/cjs/types.js +5 -0
  7. package/dist/cjs/ui/announcer.js +64 -0
  8. package/dist/cjs/ui/link-picker.js +611 -0
  9. package/dist/cjs/ui/link-search-list/index.js +102 -0
  10. package/dist/cjs/ui/link-search-list/styled.js +21 -0
  11. package/dist/cjs/ui/link-search-no-results/index.js +27 -0
  12. package/dist/cjs/ui/link-search-no-results/no-results.svg +38 -0
  13. package/dist/cjs/ui/list-item/index.js +103 -0
  14. package/dist/cjs/ui/list-item/styled.js +59 -0
  15. package/dist/cjs/ui/styled.js +32 -0
  16. package/dist/cjs/ui/text-input/index.js +116 -0
  17. package/dist/cjs/ui/text-input/styled.js +25 -0
  18. package/dist/cjs/url.js +104 -0
  19. package/dist/cjs/utils.js +27 -0
  20. package/dist/cjs/version.json +5 -0
  21. package/dist/es2019/browser.js +34 -0
  22. package/dist/es2019/index.js +1 -0
  23. package/dist/es2019/messages.js +103 -0
  24. package/dist/es2019/transformTimeStamp.js +41 -0
  25. package/dist/es2019/types.js +1 -0
  26. package/dist/es2019/ui/announcer.js +38 -0
  27. package/dist/es2019/ui/link-picker.js +499 -0
  28. package/dist/es2019/ui/link-search-list/index.js +60 -0
  29. package/dist/es2019/ui/link-search-list/styled.js +13 -0
  30. package/dist/es2019/ui/link-search-no-results/index.js +12 -0
  31. package/dist/es2019/ui/link-search-no-results/no-results.svg +38 -0
  32. package/dist/es2019/ui/list-item/index.js +88 -0
  33. package/dist/es2019/ui/list-item/styled.js +65 -0
  34. package/dist/es2019/ui/styled.js +28 -0
  35. package/dist/es2019/ui/text-input/index.js +99 -0
  36. package/dist/es2019/ui/text-input/styled.js +15 -0
  37. package/dist/es2019/url.js +88 -0
  38. package/dist/es2019/utils.js +13 -0
  39. package/dist/es2019/version.json +5 -0
  40. package/dist/esm/browser.js +34 -0
  41. package/dist/esm/index.js +1 -0
  42. package/dist/esm/messages.js +103 -0
  43. package/dist/esm/transformTimeStamp.js +41 -0
  44. package/dist/esm/types.js +1 -0
  45. package/dist/esm/ui/announcer.js +51 -0
  46. package/dist/esm/ui/link-picker.js +589 -0
  47. package/dist/esm/ui/link-search-list/index.js +88 -0
  48. package/dist/esm/ui/link-search-list/styled.js +8 -0
  49. package/dist/esm/ui/link-search-no-results/index.js +14 -0
  50. package/dist/esm/ui/link-search-no-results/no-results.svg +38 -0
  51. package/dist/esm/ui/list-item/index.js +90 -0
  52. package/dist/esm/ui/list-item/styled.js +37 -0
  53. package/dist/esm/ui/styled.js +16 -0
  54. package/dist/esm/ui/text-input/index.js +99 -0
  55. package/dist/esm/ui/text-input/styled.js +10 -0
  56. package/dist/esm/url.js +90 -0
  57. package/dist/esm/utils.js +13 -0
  58. package/dist/esm/version.json +5 -0
  59. package/dist/types/browser.d.ts +15 -0
  60. package/dist/types/index.d.ts +3 -0
  61. package/dist/types/messages.d.ts +102 -0
  62. package/dist/types/transformTimeStamp.d.ts +3 -0
  63. package/dist/types/types.d.ts +40 -0
  64. package/dist/types/ui/announcer.d.ts +22 -0
  65. package/dist/types/ui/link-picker.d.ts +51 -0
  66. package/dist/types/ui/link-search-list/index.d.ts +20 -0
  67. package/dist/types/ui/link-search-list/styled.d.ts +3 -0
  68. package/dist/types/ui/link-search-no-results/index.d.ts +4 -0
  69. package/dist/types/ui/list-item/index.d.ts +17 -0
  70. package/dist/types/ui/list-item/styled.d.ts +7 -0
  71. package/dist/types/ui/styled.d.ts +4 -0
  72. package/dist/types/ui/text-input/index.d.ts +17 -0
  73. package/dist/types/ui/text-input/styled.d.ts +2 -0
  74. package/dist/types/url.d.ts +5 -0
  75. package/dist/types/utils.d.ts +3 -0
  76. package/package.json +1 -2
  77. package/example-helpers/flight-deck/_manifest.json +0 -9
  78. package/example-helpers/flight-deck/mockFDResponse.json +0 -133
  79. package/example-helpers/mock-data.ts +0 -318
  80. package/example-helpers/providers.ts +0 -39
@@ -0,0 +1,88 @@
1
+ /** @jsx jsx */
2
+ import { Fragment } from 'react';
3
+ import { jsx } from '@emotion/react';
4
+ import { injectIntl } from 'react-intl-next';
5
+ import { transformTimeStamp } from '../../transformTimeStamp';
6
+ import { itemNameStyles, itemIconStyles, listItemContextStyles, listItemNameStyles, composeListItemStyles, imgStyles } from './styled';
7
+
8
+ const LinkSearchListItem = ({
9
+ item,
10
+ selected,
11
+ active,
12
+ id,
13
+ role,
14
+ intl,
15
+ onSelect,
16
+ onMouseEnter,
17
+ onMouseLeave
18
+ }) => {
19
+ const handleSelect = () => onSelect(item.objectId);
20
+
21
+ const handleMouseEnter = () => onMouseEnter(item.objectId);
22
+
23
+ const handleMouseLeave = () => onMouseLeave(item.objectId);
24
+
25
+ const container = item.container || null;
26
+ const date = transformTimeStamp(intl, item.lastViewedDate, item.lastUpdatedDate);
27
+ return jsx("div", {
28
+ css: composeListItemStyles(active, selected),
29
+ role: role,
30
+ id: id,
31
+ "aria-selected": selected,
32
+ "data-testid": "link-search-list-item",
33
+ onMouseEnter: handleMouseEnter,
34
+ onMouseLeave: handleMouseLeave,
35
+ onClick: handleSelect
36
+ }, jsx(ListItemIcon, {
37
+ item: item,
38
+ intl: intl
39
+ }), jsx("div", {
40
+ css: itemNameStyles
41
+ }, jsx("div", {
42
+ "data-testid": "link-search-list-item-title",
43
+ css: listItemNameStyles
44
+ }, item.name), jsx("div", {
45
+ "data-testid": "link-search-list-item-subtitle",
46
+ css: listItemContextStyles
47
+ }, container, container && date && jsx(Fragment, null, "\xA0 \u2022\xA0 "), date && jsx(Fragment, null, formatDate(date)))));
48
+ };
49
+
50
+ export default injectIntl(LinkSearchListItem);
51
+
52
+ const formatDate = date => {
53
+ return [date.pageAction, date.dateString, date.timeSince].filter(Boolean).join(' ');
54
+ };
55
+
56
+ const ListItemIcon = props => {
57
+ const {
58
+ item,
59
+ intl
60
+ } = props;
61
+ const {
62
+ icon,
63
+ iconAlt
64
+ } = item;
65
+
66
+ if (!icon) {
67
+ return null;
68
+ }
69
+
70
+ const alt = typeof iconAlt === 'string' ? iconAlt : intl.formatMessage(iconAlt);
71
+
72
+ if (typeof icon !== 'string') {
73
+ const Glyph = icon;
74
+ return jsx("span", {
75
+ css: itemIconStyles
76
+ }, jsx(Glyph, {
77
+ alt: alt
78
+ }));
79
+ }
80
+
81
+ return jsx("span", {
82
+ css: itemIconStyles
83
+ }, jsx("img", {
84
+ src: icon,
85
+ alt: alt,
86
+ css: imgStyles
87
+ }));
88
+ };
@@ -0,0 +1,65 @@
1
+ import { token } from '@atlaskit/tokens';
2
+ import { css } from '@emotion/react';
3
+ import { B50, N300, N800, N20, B400 } from '@atlaskit/theme/colors';
4
+ import { gridSize } from '@atlaskit/theme/constants'; // AFP-2532 TODO: Fix automatic suppressions below
5
+ // eslint-disable-next-line @atlassian/tangerine/import/entry-points
6
+
7
+ import { fontSizeSmall, fontSize } from '@atlaskit/theme';
8
+ export const relativeFontSizeToBase16 = px => {
9
+ if (typeof px === 'string') {
10
+ px = parseInt(px);
11
+ }
12
+
13
+ if (isNaN(px)) {
14
+ throw new Error(`Invalid font size: '${px}'`);
15
+ }
16
+
17
+ return `${px / 16}rem`;
18
+ };
19
+ const listItemBaseStyles = css`
20
+ display: flex;
21
+ padding: ${gridSize()}px ${gridSize() * 2}px;
22
+ margin: 0 -${gridSize() * 2}px;
23
+ cursor: pointer;
24
+ `;
25
+ const listItemBoxShadow = css`
26
+ box-shadow: inset 2px 0px 0px ${token('color.border.selected', B400)};
27
+ `;
28
+ const listItemActive = css`
29
+ background-color: ${token('color.background.neutral.subtle.hovered', N20)};
30
+ `;
31
+ const listItemSelected = css`
32
+ background-color: ${token('color.background.selected', B50)};
33
+ `;
34
+ export const composeListItemStyles = (active = false, selected = false) => {
35
+ const hasShadow = active || selected;
36
+ return css`
37
+ ${listItemBaseStyles};
38
+ ${active && listItemActive};
39
+ ${selected && listItemSelected};
40
+ ${hasShadow && listItemBoxShadow};
41
+ `;
42
+ };
43
+ export const itemNameStyles = css`
44
+ overflow: hidden;
45
+ `;
46
+ export const listItemNameStyles = css`
47
+ color: ${token('color.text', N800)};
48
+ overflow: hidden;
49
+ text-overflow: ellipsis;
50
+ white-space: nowrap;
51
+ line-height: 20px;
52
+ `;
53
+ export const listItemContextStyles = css`
54
+ color: ${token('color.text', N300)};
55
+ font-size: ${relativeFontSizeToBase16(fontSizeSmall())};
56
+ line-height: ${fontSize()}px;
57
+ `;
58
+ export const itemIconStyles = css`
59
+ min-width: ${gridSize() * 2}px;
60
+ margin-top: 3px;
61
+ margin-right: 12px;
62
+ `;
63
+ export const imgStyles = css`
64
+ max-width: ${gridSize() * 2}px;
65
+ `;
@@ -0,0 +1,28 @@
1
+ import { token } from '@atlaskit/tokens';
2
+ import { css } from '@emotion/react';
3
+ import { N500 } from '@atlaskit/theme/colors'; // AFP-2532 TODO: Fix automatic suppressions below
4
+ // eslint-disable-next-line @atlassian/tangerine/import/entry-points
5
+
6
+ import { typography } from '@atlaskit/theme';
7
+ import { gridSize } from '@atlaskit/theme/constants';
8
+ const LINK_PICKER_WIDTH_IN_PX = 342;
9
+ export const recentListStyles = css`
10
+ width: ${LINK_PICKER_WIDTH_IN_PX}px;
11
+ `;
12
+ export const containerStyles = css`
13
+ padding: ${gridSize() * 2}px;
14
+ width: 100%;
15
+ overflow: hidden;
16
+ line-height: initial;
17
+ box-sizing: border-box;
18
+ `;
19
+ export const searchIconStyles = css`
20
+ margin-left: ${gridSize() / 2}px;
21
+ color: ${token('color.icon', N500)};
22
+ cursor: default;
23
+ `;
24
+ export const listTitleStyles = css`
25
+ ${typography.h100()}
26
+ text-transform: uppercase;
27
+ margin-bottom: ${gridSize() / 2}px;
28
+ `;
@@ -0,0 +1,99 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+
3
+ /** @jsx jsx */
4
+ import { useCallback, useRef, Fragment } from 'react';
5
+ import { jsx } from '@emotion/react';
6
+ import { ErrorMessage, Field } from '@atlaskit/form';
7
+ import Tooltip from '@atlaskit/tooltip';
8
+ import Textfield from '@atlaskit/textfield';
9
+ import Selectclear from '@atlaskit/icon/glyph/select-clear';
10
+ import { isRedoEvent, isUndoEvent } from '../../utils';
11
+ import { clearTextButtonStyles, fieldStyles } from './styled';
12
+
13
+ const PanelTextInput = props => {
14
+ const {
15
+ name,
16
+ label,
17
+ autoFocus,
18
+ onSubmit,
19
+ onRedo,
20
+ onUndo,
21
+ onChange,
22
+ onKeyDown,
23
+ onClear,
24
+ clearLabel,
25
+ error,
26
+ ...restProps
27
+ } = props;
28
+ const inputRef = useRef(null);
29
+ const handleRef = useCallback(input => {
30
+ if (input) {
31
+ inputRef.current = input;
32
+
33
+ if (autoFocus) {
34
+ // Need this to prevent jumping when we render TextInput inside Portal @see ED-2992
35
+ input.focus({
36
+ preventScroll: true
37
+ });
38
+ }
39
+ }
40
+ }, [autoFocus]);
41
+ const handleChange = useCallback(e => {
42
+ onChange(e.currentTarget.value);
43
+ }, [onChange]);
44
+ const handleKeydown = useCallback(e => {
45
+ const value = e.currentTarget.value;
46
+
47
+ if (e.keyCode === 13 && onSubmit) {
48
+ e.preventDefault(); // Prevent from submitting if an editor is inside a form.
49
+
50
+ onSubmit(value);
51
+ } else if (typeof onUndo === 'function' && isUndoEvent(e)) {
52
+ e.preventDefault();
53
+ onUndo();
54
+ } else if (typeof onRedo === 'function' && isRedoEvent(e)) {
55
+ e.preventDefault();
56
+ onRedo();
57
+ }
58
+
59
+ if (onKeyDown) {
60
+ onKeyDown(e);
61
+ }
62
+ }, [onSubmit, onUndo, onRedo, onKeyDown]);
63
+ const handleClear = useCallback(() => {
64
+ if (inputRef.current && onClear) {
65
+ inputRef.current.focus();
66
+ onClear();
67
+ }
68
+ }, [onClear]);
69
+ const clearText = restProps.value !== '' && jsx(Tooltip, {
70
+ content: clearLabel
71
+ }, jsx("button", {
72
+ css: clearTextButtonStyles,
73
+ onClick: handleClear,
74
+ "data-testid": "clear-text"
75
+ }, jsx(Selectclear, {
76
+ size: "medium",
77
+ label: clearLabel || ''
78
+ })));
79
+ return jsx("div", {
80
+ css: fieldStyles
81
+ }, jsx(Field, {
82
+ label: label,
83
+ name: name
84
+ }, ({
85
+ fieldProps
86
+ }) => {
87
+ return jsx(Fragment, null, jsx(Textfield, _extends({}, fieldProps, restProps, {
88
+ onKeyDown: handleKeydown,
89
+ onChange: handleChange,
90
+ ref: handleRef,
91
+ elemAfterInput: clearText,
92
+ isInvalid: !!error
93
+ })), error && jsx(ErrorMessage, {
94
+ testId: "link-error"
95
+ }, error));
96
+ }));
97
+ };
98
+
99
+ export default PanelTextInput;
@@ -0,0 +1,15 @@
1
+ import { token } from '@atlaskit/tokens';
2
+ import { css } from '@emotion/react';
3
+ import { N500 } from '@atlaskit/theme/colors';
4
+ import { gridSize } from '@atlaskit/theme/constants';
5
+ export const clearTextButtonStyles = css`
6
+ padding: 0;
7
+ margin-right: ${gridSize() / 2}px;
8
+ color: ${token('color.icon.subtle', N500)};
9
+ background: transparent;
10
+ border: none;
11
+ cursor: pointer;
12
+ `;
13
+ export const fieldStyles = css`
14
+ margin-bottom: ${gridSize() * 2}px;
15
+ `;
@@ -0,0 +1,88 @@
1
+ import LinkifyIt from 'linkify-it';
2
+ const linkify = LinkifyIt();
3
+ linkify.add('sourcetree:', 'http:');
4
+ const tlds = 'biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф'.split('|');
5
+ const tlds2Char = 'a[cdefgilmnoqrtuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvwxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrtuvwxyz]|n[acefgilopruz]|om|p[aefghkmnrtw]|qa|r[eosuw]|s[abcdegijklmnrtuvxyz]|t[cdfghjklmnortvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]';
6
+ tlds.push(tlds2Char);
7
+ linkify.tlds(tlds, false);
8
+ const whitelistedURLPatterns = [/^https?:\/\/[^\s]*$/im, /^ftps?:\/\//im, /^\//im, /^mailto:/im, /^skype:/im, /^callto:[^\s]*$/im, /^facetime:[^\s]*$/im, /^git:/im, /^irc6?:/im, /^news:/im, /^nntp:/im, /^feed:/im, /^cvs:/im, /^svn:/im, /^mvn:/im, /^ssh:/im, /^scp:\/\//im, /^sftp:\/\//im, /^itms:/im, /^notes:/im, /^hipchat:\/\//im, /^sourcetree:/im, /^urn:/im, /^tel:/im, /^xmpp:/im, /^telnet:/im, /^vnc:/im, /^rdp:/im, /^whatsapp:/im, /^slack:/im, /^sips?:/im, /^magnet:/im, /^foo:/im, /^#/im];
9
+
10
+ const normaliseLinkHref = url => {
11
+ const match = getLinkMatch(url);
12
+ return match && match.url || null;
13
+ };
14
+
15
+ const getLinkMatch = str => {
16
+ if (!str) {
17
+ return null;
18
+ }
19
+
20
+ let match = linkifyMatch(str);
21
+
22
+ if (!match.length) {
23
+ match = linkify.match(str);
24
+ }
25
+
26
+ return match && match.length > 0 && match[0].index === 0 && match[0].lastIndex === str.trim().length ? match[0] : null;
27
+ };
28
+
29
+ const LINK_REGEXP = /(https?|ftp):\/\/[^\s]+/;
30
+
31
+ const linkifyMatch = text => {
32
+ const matches = [];
33
+
34
+ if (!LINK_REGEXP.test(text)) {
35
+ return matches;
36
+ }
37
+
38
+ let startpos = 0;
39
+ let substr;
40
+
41
+ while (substr = text.substr(startpos)) {
42
+ const link = (substr.match(LINK_REGEXP) || [''])[0];
43
+
44
+ if (link) {
45
+ const index = substr.search(LINK_REGEXP);
46
+ const start = index >= 0 ? index + startpos : index;
47
+ const end = start + link.length;
48
+ matches.push({
49
+ index: start,
50
+ lastIndex: end,
51
+ raw: link,
52
+ url: link,
53
+ text: link,
54
+ schema: ''
55
+ });
56
+ startpos += end;
57
+ } else {
58
+ break;
59
+ }
60
+ }
61
+
62
+ return matches;
63
+ };
64
+
65
+ export const isSafeUrl = url => {
66
+ const urlTrimmed = url.trim();
67
+
68
+ if (urlTrimmed.length === 0) {
69
+ return true;
70
+ }
71
+
72
+ return whitelistedURLPatterns.some(p => p.test(urlTrimmed));
73
+ };
74
+ /**
75
+ * Adds protocol to url if needed.
76
+ */
77
+
78
+ export function normalizeUrl(url) {
79
+ if (!url) {
80
+ return '';
81
+ }
82
+
83
+ if (isSafeUrl(url)) {
84
+ return url.trim();
85
+ }
86
+
87
+ return normaliseLinkHref(url);
88
+ }
@@ -0,0 +1,13 @@
1
+ import { browser } from './browser';
2
+ const KeyZCode = 90;
3
+ const KeyYCode = 89;
4
+ export const isUndoEvent = e => {
5
+ return e.keyCode === KeyZCode && ( // cmd + z for mac
6
+ browser.mac && e.metaKey && !e.shiftKey || // ctrl + z for non-mac
7
+ !browser.mac && e.ctrlKey);
8
+ };
9
+ export const isRedoEvent = e => {
10
+ return (// ctrl + y for non-mac
11
+ !browser.mac && e.ctrlKey && e.keyCode === KeyYCode || browser.mac && e.metaKey && e.shiftKey && e.keyCode === KeyZCode || e.ctrlKey && e.shiftKey && e.keyCode === KeyZCode
12
+ );
13
+ };
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@atlaskit/link-picker",
3
+ "version": "1.1.2",
4
+ "sideEffects": false
5
+ }
@@ -0,0 +1,34 @@
1
+ var result = {
2
+ mac: false,
3
+ ie: false,
4
+ ie_version: 0,
5
+ gecko: false,
6
+ chrome: false,
7
+ chrome_version: 0,
8
+ android: false,
9
+ ios: false,
10
+ webkit: false,
11
+ safari: false,
12
+ supportsIntersectionObserver: false,
13
+ supportsResizeObserver: false
14
+ };
15
+
16
+ if (typeof navigator !== 'undefined') {
17
+ var ieEdge = /Edge\/(\d+)/.exec(navigator.userAgent);
18
+ var ieUpTo10 = /MSIE \d/.test(navigator.userAgent);
19
+ var ie11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);
20
+ result.mac = /Mac/.test(navigator.platform);
21
+ var ie = result.ie = !!(ieUpTo10 || ie11up || ieEdge);
22
+ result.ie_version = ieUpTo10 ? document.documentMode || 6 : ie11up ? +ie11up[1] : ieEdge ? +ieEdge[1] : null;
23
+ result.gecko = !ie && /gecko\/\d/i.test(navigator.userAgent);
24
+ result.chrome = !ie && /Chrome\//.test(navigator.userAgent);
25
+ result.chrome_version = parseInt((navigator.userAgent.match(/Chrome\/(\d+)/) || [])[1], 10);
26
+ result.android = /Android \d/.test(navigator.userAgent);
27
+ result.ios = !ie && /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
28
+ result.webkit = !ie && !!document.documentElement && 'WebkitAppearance' in document.documentElement.style;
29
+ result.safari = Boolean(navigator.vendor && navigator.vendor.indexOf('Apple') > -1 && navigator.userAgent && navigator.userAgent.indexOf('CriOS') === -1 && navigator.userAgent.indexOf('FxiOS') === -1);
30
+ result.supportsIntersectionObserver = typeof window !== 'undefined' && 'IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in window.IntersectionObserverEntry.prototype;
31
+ result.supportsResizeObserver = typeof window !== 'undefined' && 'ResizeObserver' in window && 'ResizeObserverEntry' in window;
32
+ }
33
+
34
+ export { result as browser };
@@ -0,0 +1 @@
1
+ export { LinkPickerWithIntl as LinkPicker } from './ui/link-picker';
@@ -0,0 +1,103 @@
1
+ import { defineMessages } from 'react-intl-next';
2
+ export var messages = defineMessages({
3
+ placeholder: {
4
+ id: 'fabric.linkPicker.hyperlinkToolbarPlaceholder',
5
+ defaultMessage: 'Search or paste a link',
6
+ description: 'Search or paste a link'
7
+ },
8
+ linkPlaceholder: {
9
+ id: 'fabric.linkPicker.linkPlaceholder',
10
+ defaultMessage: 'Paste link',
11
+ description: 'Create a new link by pasting a URL.'
12
+ },
13
+ linkTextPlaceholder: {
14
+ id: 'fabric.linkPicker.linkTextPlaceholder',
15
+ defaultMessage: 'Add a description',
16
+ description: 'Text to display'
17
+ },
18
+ linkLabel: {
19
+ id: 'fabric.linkPicker.linkAddress',
20
+ defaultMessage: 'Link',
21
+ description: 'Link Label'
22
+ },
23
+ linkInvalid: {
24
+ id: 'fabric.linkPicker.linkInvalid',
25
+ defaultMessage: 'Enter a valid URL.',
26
+ description: 'Input Error the URL is invalid'
27
+ },
28
+ linkTextLabel: {
29
+ id: 'fabric.linkPicker.displayText',
30
+ defaultMessage: 'Link text',
31
+ description: 'Link text label'
32
+ },
33
+ clearText: {
34
+ id: 'fabric.linkPicker.clearLinkText',
35
+ defaultMessage: 'Clear text',
36
+ description: 'Clears text on the link toolbar'
37
+ },
38
+ clearLink: {
39
+ id: 'fabric.linkPicker.clearLink',
40
+ defaultMessage: 'Clear link',
41
+ description: 'Clears link in the link toolbar'
42
+ },
43
+ searchLinkAriaDescription: {
44
+ id: 'fabric.linkPicker.hyperlink.searchLinkAriaDescription',
45
+ defaultMessage: 'Suggestions will appear below as you type into the field',
46
+ description: 'Describes what the search field does for screen reader users.'
47
+ },
48
+ searchLinkResults: {
49
+ id: 'fabric.linkPicker.hyperlink.searchLinkResults',
50
+ defaultMessage: '{count, plural, =0 {no results} one {# result} other {# results}} found',
51
+ description: 'Announce search results for screen-reader users.'
52
+ },
53
+ linkAriaLabel: {
54
+ id: 'fabric.linkPicker.hyperlink.linkAriaLabel',
55
+ defaultMessage: 'Link label',
56
+ description: 'aria label for a link'
57
+ },
58
+ timeUpdated: {
59
+ id: 'fabric.linkPicker.time.updated',
60
+ defaultMessage: 'Updated',
61
+ description: 'Time last updated'
62
+ },
63
+ timeViewed: {
64
+ id: 'fabric.linkPicker.time.viewed',
65
+ defaultMessage: 'Viewed',
66
+ description: 'Time last viewed'
67
+ },
68
+ timeAgo: {
69
+ id: 'fabric.linkPicker.time.ago',
70
+ defaultMessage: 'ago',
71
+ description: 'Some time ago'
72
+ },
73
+ titleRecentlyViewed: {
74
+ id: 'fabric.linkPicker.listTitle.recentlyViewed',
75
+ defaultMessage: 'Recently Viewed',
76
+ description: 'Describes type of items shown in the list for screen-reader users'
77
+ },
78
+ titleResults: {
79
+ id: 'fabric.linkPicker.listTitle.results',
80
+ defaultMessage: 'Results',
81
+ description: 'Describes type of items shown in the list for screen-reader users'
82
+ },
83
+ cancelButton: {
84
+ id: 'fabric.linkPicker.button.cancel',
85
+ defaultMessage: 'Cancel',
86
+ description: 'Button to cancel and dismiss the link picker'
87
+ },
88
+ insertButton: {
89
+ id: 'fabric.linkPicker.button.insert',
90
+ defaultMessage: 'Insert',
91
+ description: 'Button to insert searched or selected link'
92
+ },
93
+ noResults: {
94
+ id: 'fabric.linkPicker.search.noResults.heading',
95
+ defaultMessage: 'We couldn’t find anything matching your search.',
96
+ description: 'Heading message shown when a search has no results'
97
+ },
98
+ noResultsDescription: {
99
+ id: 'fabric.linkPicker.search.noResults.description',
100
+ defaultMessage: 'Try again with a different term.',
101
+ description: 'Describes possible action when a search returns no results'
102
+ }
103
+ });
@@ -0,0 +1,41 @@
1
+ import formatDistance from 'date-fns/formatDistance';
2
+ import differenceInCalendarDays from 'date-fns/differenceInCalendarDays';
3
+ import format from 'date-fns/format';
4
+ import { messages } from './messages';
5
+
6
+ var renderAbsoluteOrRelativeDate = function renderAbsoluteOrRelativeDate(timeStamp, pageAction, intl) {
7
+ var pageActionText = '';
8
+
9
+ switch (pageAction) {
10
+ case 'updated':
11
+ pageActionText = intl.formatMessage(messages.timeUpdated);
12
+ break;
13
+
14
+ case 'viewed':
15
+ pageActionText = intl.formatMessage(messages.timeViewed);
16
+ break;
17
+ }
18
+
19
+ if (differenceInCalendarDays(timeStamp, Date.now()) < -7) {
20
+ return {
21
+ pageAction: pageActionText,
22
+ dateString: format(timeStamp, 'MMMM dd, yyyy')
23
+ };
24
+ }
25
+
26
+ return {
27
+ pageAction: pageActionText,
28
+ dateString: formatDistance(timeStamp, Date.now()),
29
+ timeSince: intl.formatMessage(messages.timeAgo)
30
+ };
31
+ };
32
+
33
+ export var transformTimeStamp = function transformTimeStamp(intl, lastViewedDate, lastUpdatedDate) {
34
+ if (lastViewedDate) {
35
+ return renderAbsoluteOrRelativeDate(lastViewedDate, 'viewed', intl);
36
+ }
37
+
38
+ if (lastUpdatedDate) {
39
+ return renderAbsoluteOrRelativeDate(lastUpdatedDate, 'updated', intl);
40
+ }
41
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
+ import React, { forwardRef, memo, useState, useEffect } from 'react'; // How to use:
3
+ // https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions#Preferring_specialized_live_region_roles
4
+ // Demo: https://dequeuniversity.com/library/aria/liveregion-playground
5
+ // Important: Strongly recommend test your solution in all supported screen readers
6
+ // if you use non default value of properties: ariaAtomic, ariaLive, ariaRelevant, role
7
+ // Note: Text won't be announced if the text message doesn't change after the render.
8
+ // For using a forced announcement in this case, set the 'key' attribute - key={Date.now()}
9
+
10
+ // Note: Flag 'contentRendered' resolves bug with duplicates messages (NVDA + Firefox)
11
+ // https://github.com/nvaccess/nvda/labels/bug%2Fdouble-speaking
12
+ var timer;
13
+ var Announcer = /*#__PURE__*/forwardRef(function (_ref, ref) {
14
+ var _ref$ariaAtomic = _ref.ariaAtomic,
15
+ ariaAtomic = _ref$ariaAtomic === void 0 ? 'true' : _ref$ariaAtomic,
16
+ _ref$ariaLive = _ref.ariaLive,
17
+ ariaLive = _ref$ariaLive === void 0 ? 'polite' : _ref$ariaLive,
18
+ _ref$ariaRelevant = _ref.ariaRelevant,
19
+ ariaRelevant = _ref$ariaRelevant === void 0 ? 'all' : _ref$ariaRelevant,
20
+ _ref$role = _ref.role,
21
+ role = _ref$role === void 0 ? 'status' : _ref$role,
22
+ _ref$text = _ref.text,
23
+ text = _ref$text === void 0 ? '' : _ref$text,
24
+ _ref$delay = _ref.delay,
25
+ delay = _ref$delay === void 0 ? 0 : _ref$delay;
26
+
27
+ var _useState = useState(false),
28
+ _useState2 = _slicedToArray(_useState, 2),
29
+ contentRendered = _useState2[0],
30
+ setContentRendered = _useState2[1];
31
+
32
+ useEffect(function () {
33
+ clearTimeout(timer);
34
+ setContentRendered(false);
35
+ timer = setTimeout(function () {
36
+ setContentRendered(true);
37
+ }, delay);
38
+ return function () {
39
+ return clearTimeout(timer);
40
+ };
41
+ }, [text, delay]);
42
+ return /*#__PURE__*/React.createElement("div", {
43
+ className: "assistive",
44
+ ref: ref,
45
+ role: role,
46
+ "aria-atomic": ariaAtomic,
47
+ "aria-relevant": ariaRelevant,
48
+ "aria-live": ariaLive
49
+ }, contentRendered && /*#__PURE__*/React.createElement("span", null, text));
50
+ });
51
+ export default /*#__PURE__*/memo(Announcer);