@atlaskit/link-picker 1.9.2 → 1.9.3
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.
- package/CHANGELOG.md +6 -0
- package/dist/cjs/analytics.codegen.js +1 -1
- package/dist/cjs/common/analytics/context.js +137 -0
- package/dist/cjs/common/analytics/index.js +27 -0
- package/dist/cjs/common/analytics/input-field-tracking.js +115 -0
- package/dist/cjs/common/constants.js +1 -1
- package/dist/cjs/ui/index.js +2 -1
- package/dist/cjs/ui/link-picker/index.js +56 -16
- package/dist/cjs/ui/link-picker/text-input/index.js +18 -23
- package/dist/cjs/version.json +1 -1
- package/dist/es2019/analytics.codegen.js +1 -1
- package/dist/es2019/common/analytics/context.js +95 -0
- package/dist/es2019/common/analytics/index.js +2 -0
- package/dist/es2019/common/analytics/input-field-tracking.js +85 -0
- package/dist/es2019/common/constants.js +1 -1
- package/dist/es2019/ui/index.js +2 -1
- package/dist/es2019/ui/link-picker/index.js +51 -14
- package/dist/es2019/ui/link-picker/link-search-error/index.js +2 -2
- package/dist/es2019/ui/link-picker/text-input/index.js +14 -20
- package/dist/es2019/version.json +1 -1
- package/dist/esm/analytics.codegen.js +1 -1
- package/dist/esm/common/analytics/context.js +110 -0
- package/dist/esm/common/analytics/index.js +2 -0
- package/dist/esm/common/analytics/input-field-tracking.js +91 -0
- package/dist/esm/common/constants.js +1 -1
- package/dist/esm/ui/index.js +2 -1
- package/dist/esm/ui/link-picker/index.js +54 -14
- package/dist/esm/ui/link-picker/link-search-error/index.js +2 -2
- package/dist/esm/ui/link-picker/text-input/index.js +16 -21
- package/dist/esm/version.json +1 -1
- package/dist/types/analytics.codegen.d.ts +9 -1
- package/dist/types/common/analytics/context.d.ts +28 -0
- package/dist/types/common/analytics/index.d.ts +2 -0
- package/dist/types/common/analytics/input-field-tracking.d.ts +17 -0
- package/dist/types/common/constants.d.ts +1 -1
- package/dist/types/ui/index.d.ts +1 -0
- package/dist/types/ui/link-picker/text-input/index.d.ts +6 -8
- package/package.json +1 -1
- package/dist/cjs/services/with-link-picker-analytics/index.js +0 -33
- package/dist/es2019/services/with-link-picker-analytics/index.js +0 -15
- package/dist/esm/services/with-link-picker-analytics/index.js +0 -17
- package/dist/types/services/with-link-picker-analytics/index.d.ts +0 -4
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
import React, { useCallback, useRef } from 'react';
|
|
3
|
+
import { useAnalyticsEvents } from '@atlaskit/analytics-next';
|
|
4
|
+
import { ANALYTICS_CHANNEL } from '../constants';
|
|
5
|
+
import createEventPayload from '../../analytics.codegen';
|
|
6
|
+
import { useLinkPickerAnalytics } from '.';
|
|
7
|
+
|
|
8
|
+
const isEventWithInputType = e => {
|
|
9
|
+
return typeof e === 'object' && e !== null && 'inputType' in e;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const getChangeEventInputMethodType = event => {
|
|
13
|
+
if (isEventWithInputType(event.nativeEvent) && typeof event.nativeEvent.inputType === 'string' && event.nativeEvent.inputType.includes('insertFromPaste')) {
|
|
14
|
+
return 'paste';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return 'manual';
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Wraps an input field and connects it to the link picker analytics context to provide a baseline of analytics tracking
|
|
21
|
+
* @param WrappedComponent The input field to be tracked
|
|
22
|
+
* @param field The name of the field (ie matching actionSubjectId)
|
|
23
|
+
* @param filterOnChangeEventAttrs Function to hook into and filter/customise the default attribute tracking logic
|
|
24
|
+
* @returns HOC Input field with tracking
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
export const withInputFieldTracking = (WrappedComponent, field, filterOnChangeEventAttrs = (_, attributes) => attributes // TODO: Consider refactor to use template literal type if it makes these more usable / safe / dev friendly
|
|
29
|
+
// contentTypeFn: (value: string) => LinkPickerAnalyticsContextType[`${Field}FieldContent`],
|
|
30
|
+
) => {
|
|
31
|
+
return props => {
|
|
32
|
+
const {
|
|
33
|
+
onBlur,
|
|
34
|
+
onChange,
|
|
35
|
+
onClear,
|
|
36
|
+
onFocus
|
|
37
|
+
} = props;
|
|
38
|
+
const {
|
|
39
|
+
trackAttribute,
|
|
40
|
+
trackAttributes
|
|
41
|
+
} = useLinkPickerAnalytics();
|
|
42
|
+
const {
|
|
43
|
+
createAnalyticsEvent
|
|
44
|
+
} = useAnalyticsEvents(); // Ref to track if the value of the field has changed between focus and blur
|
|
45
|
+
|
|
46
|
+
const valueOnFocus = useRef(null); // Track the content and input method on every direct change to the input
|
|
47
|
+
|
|
48
|
+
const handleOnChange = useCallback(event => {
|
|
49
|
+
// TODO: See above re template literal, improve typesafety here
|
|
50
|
+
// trackAttribute(`${field}FieldContent`, contentTypeFn(event.currentTarget.value));
|
|
51
|
+
// trackAttribute(`${field}FieldContentInputMethod`, methodTypeFn(event));
|
|
52
|
+
trackAttributes(filterOnChangeEventAttrs(event, {
|
|
53
|
+
[`${field}FieldContent`]: Boolean(event.currentTarget.value) ? 'text_string' : null,
|
|
54
|
+
[`${field}FieldContentInputMethod`]: getChangeEventInputMethodType(event)
|
|
55
|
+
}));
|
|
56
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event);
|
|
57
|
+
}, [onChange, trackAttributes]); // Store the value of the field at the time the field is focused so we can know if it changes when the user's focus leaves
|
|
58
|
+
|
|
59
|
+
const handleOnFocus = useCallback(event => {
|
|
60
|
+
valueOnFocus.current = event.currentTarget.value;
|
|
61
|
+
onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
|
|
62
|
+
}, [onFocus]); // Fire updated events only when the user's focus leaves the field
|
|
63
|
+
|
|
64
|
+
const handleOnBlur = useCallback(event => {
|
|
65
|
+
// Only fire if the value has changed since the field was focused (we are only concerned with the net change to the field)
|
|
66
|
+
if (event.currentTarget.value !== valueOnFocus.current) {
|
|
67
|
+
createAnalyticsEvent(createEventPayload(`ui.textField.updated.${field}Field`, {})).fire(ANALYTICS_CHANNEL);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
|
|
71
|
+
}, [onBlur, createAnalyticsEvent]); // Track the values as null when the text field is cleared
|
|
72
|
+
|
|
73
|
+
const handleOnClear = useCallback((...args) => {
|
|
74
|
+
trackAttribute(`${field}FieldContent`, null);
|
|
75
|
+
trackAttribute(`${field}FieldContentInputMethod`, null);
|
|
76
|
+
onClear === null || onClear === void 0 ? void 0 : onClear(...args);
|
|
77
|
+
}, [onClear, trackAttribute]);
|
|
78
|
+
return /*#__PURE__*/React.createElement(WrappedComponent, _extends({}, props, {
|
|
79
|
+
onBlur: handleOnBlur,
|
|
80
|
+
onChange: handleOnChange,
|
|
81
|
+
onClear: handleOnClear,
|
|
82
|
+
onFocus: handleOnFocus
|
|
83
|
+
}));
|
|
84
|
+
};
|
|
85
|
+
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const ANALYTICS_CHANNEL = 'media';
|
|
2
|
-
export const COMPONENT_NAME = '
|
|
2
|
+
export const COMPONENT_NAME = 'linkPicker';
|
package/dist/es2019/ui/index.js
CHANGED
|
@@ -8,7 +8,8 @@ import { LoaderFallback } from './loader-fallback';
|
|
|
8
8
|
export const PACKAGE_DATA = {
|
|
9
9
|
packageName,
|
|
10
10
|
packageVersion,
|
|
11
|
-
componentName: COMPONENT_NAME
|
|
11
|
+
componentName: COMPONENT_NAME,
|
|
12
|
+
source: COMPONENT_NAME
|
|
12
13
|
};
|
|
13
14
|
const LazyLinkPicker = lazyForPaint(() => import(
|
|
14
15
|
/* webpackChunkName: "@atlaskit-internal_link-picker" */
|
|
@@ -10,13 +10,13 @@ import Tabs, { Tab, TabList } from '@atlaskit/tabs';
|
|
|
10
10
|
import VisuallyHidden from '@atlaskit/visually-hidden';
|
|
11
11
|
import createEventPayload from '../../analytics.codegen';
|
|
12
12
|
import { ANALYTICS_CHANNEL } from '../../common/constants';
|
|
13
|
+
import { useLinkPickerAnalytics, withInputFieldTracking, withLinkPickerAnalyticsContext } from '../../common/analytics';
|
|
14
|
+
import { normalizeUrl, isSafeUrl } from '../../common/utils/url';
|
|
13
15
|
import { usePlugins } from '../../services/use-plugins';
|
|
14
16
|
import { useSearchQuery } from '../../services/use-search-query';
|
|
15
|
-
import { withLinkPickerAnalyticsContext } from '../../services/with-link-picker-analytics';
|
|
16
|
-
import { messages } from './messages';
|
|
17
|
-
import PanelTextInput from './text-input';
|
|
18
|
-
import { normalizeUrl, isSafeUrl } from '../../common/utils/url';
|
|
19
17
|
import useFixHeight from '../../controllers/use-fix-height';
|
|
18
|
+
import { messages } from './messages';
|
|
19
|
+
import TextInput from './text-input';
|
|
20
20
|
import { rootContainerStyles, searchIconStyles, tabsWrapperStyles, flexColumnStyles, formFooterMargin } from './styled';
|
|
21
21
|
import { browser } from './browser';
|
|
22
22
|
import Announcer from './announcer';
|
|
@@ -61,6 +61,23 @@ function reducer(state, payload) {
|
|
|
61
61
|
...payload
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Bind input fields to analytics tracking
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
const getLinkFieldContent = value => {
|
|
70
|
+
if (!Boolean(value)) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return isSafeUrl(value) ? 'url' : 'text_string';
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const LinkInputField = withInputFieldTracking(TextInput, 'link', (event, attributes) => ({ ...attributes,
|
|
78
|
+
linkFieldContent: getLinkFieldContent(event.currentTarget.value)
|
|
79
|
+
}));
|
|
80
|
+
const DisplayTextInputField = withInputFieldTracking(TextInput, 'displayText');
|
|
64
81
|
|
|
65
82
|
function LinkPicker({
|
|
66
83
|
onSubmit,
|
|
@@ -102,6 +119,10 @@ function LinkPicker({
|
|
|
102
119
|
const isEditing = !!initUrl;
|
|
103
120
|
const selectedItem = items === null || items === void 0 ? void 0 : items[selectedIndex];
|
|
104
121
|
const isSelectedItem = (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.url) === url;
|
|
122
|
+
const {
|
|
123
|
+
trackAttribute,
|
|
124
|
+
getAttributes
|
|
125
|
+
} = useLinkPickerAnalytics();
|
|
105
126
|
useEffect(() => {
|
|
106
127
|
// Anything in here is fired on component mount.
|
|
107
128
|
const event = createAnalyticsEvent(createEventPayload('ui.inlineDialog.viewed.linkPicker', {}));
|
|
@@ -117,14 +138,14 @@ function LinkPicker({
|
|
|
117
138
|
onContentResize();
|
|
118
139
|
}
|
|
119
140
|
}, [onContentResize, items, isLoading, isActivePlugin, tabs]);
|
|
120
|
-
const handleChangeUrl = useCallback(
|
|
141
|
+
const handleChangeUrl = useCallback(e => {
|
|
121
142
|
dispatch({
|
|
122
|
-
url
|
|
143
|
+
url: e.currentTarget.value
|
|
123
144
|
});
|
|
124
145
|
}, []);
|
|
125
|
-
const handleChangeText = useCallback(
|
|
146
|
+
const handleChangeText = useCallback(e => {
|
|
126
147
|
dispatch({
|
|
127
|
-
displayText
|
|
148
|
+
displayText: e.currentTarget.value
|
|
128
149
|
});
|
|
129
150
|
}, []);
|
|
130
151
|
const handleKeyDown = useCallback(event => {
|
|
@@ -155,6 +176,11 @@ function LinkPicker({
|
|
|
155
176
|
}
|
|
156
177
|
|
|
157
178
|
if ([KEY_CODE_ARROW_DOWN, KEY_CODE_ARROW_UP].includes(keyCode) && items[updatedIndex]) {
|
|
179
|
+
/**
|
|
180
|
+
* Manually track that the url has been updated using searchResult method
|
|
181
|
+
*/
|
|
182
|
+
trackAttribute('linkFieldContent', getLinkFieldContent(items[updatedIndex].url));
|
|
183
|
+
trackAttribute('linkFieldContentInputMethod', 'searchResult');
|
|
158
184
|
dispatch({
|
|
159
185
|
activeIndex: updatedIndex,
|
|
160
186
|
selectedIndex: updatedIndex,
|
|
@@ -162,7 +188,7 @@ function LinkPicker({
|
|
|
162
188
|
invalidUrl: false
|
|
163
189
|
});
|
|
164
190
|
}
|
|
165
|
-
}, [activeIndex, items]);
|
|
191
|
+
}, [activeIndex, items, trackAttribute]);
|
|
166
192
|
const handleClear = useCallback(field => {
|
|
167
193
|
dispatch({
|
|
168
194
|
[field]: ''
|
|
@@ -187,7 +213,12 @@ function LinkPicker({
|
|
|
187
213
|
const event = createAnalyticsEvent(createEventPayload('ui.form.submitted.linkPicker', {})); // Clone the event so that it can be emitted for consumer usage
|
|
188
214
|
// This must happen BEFORE the original event is fired!
|
|
189
215
|
|
|
190
|
-
const consumerEvent = event.clone(); //
|
|
216
|
+
const consumerEvent = event.clone(); // Cloned event doesnt have the attributes that are added by
|
|
217
|
+
// the analytics listener in the LinkPickerAnalyticsContext, add them here
|
|
218
|
+
|
|
219
|
+
consumerEvent === null || consumerEvent === void 0 ? void 0 : consumerEvent.update({
|
|
220
|
+
attributes: getAttributes()
|
|
221
|
+
}); // Dispatch the original event to our channel
|
|
191
222
|
|
|
192
223
|
event.fire(ANALYTICS_CHANNEL);
|
|
193
224
|
onSubmit({
|
|
@@ -201,7 +232,7 @@ function LinkPicker({
|
|
|
201
232
|
rawUrl: state.url
|
|
202
233
|
} : {})
|
|
203
234
|
}, consumerEvent);
|
|
204
|
-
}, [displayText, onSubmit, state.url, createAnalyticsEvent]);
|
|
235
|
+
}, [displayText, onSubmit, state.url, createAnalyticsEvent, getAttributes]);
|
|
205
236
|
const handleSelected = useCallback(objectId => {
|
|
206
237
|
const selectedItem = items === null || items === void 0 ? void 0 : items.find(item => item.objectId === objectId);
|
|
207
238
|
|
|
@@ -211,9 +242,15 @@ function LinkPicker({
|
|
|
211
242
|
name
|
|
212
243
|
} = selectedItem;
|
|
213
244
|
dispatchEvent(new Event('submit'));
|
|
245
|
+
/**
|
|
246
|
+
* Manually track that the url has been updated using searchResult method
|
|
247
|
+
*/
|
|
248
|
+
|
|
249
|
+
trackAttribute('linkFieldContent', getLinkFieldContent(url));
|
|
250
|
+
trackAttribute('linkFieldContentInputMethod', 'searchResult');
|
|
214
251
|
handleInsert(url, name, 'typeAhead');
|
|
215
252
|
}
|
|
216
|
-
}, [handleInsert, items]);
|
|
253
|
+
}, [handleInsert, trackAttribute, items]);
|
|
217
254
|
const handleSubmit = useCallback(event => {
|
|
218
255
|
event.preventDefault();
|
|
219
256
|
|
|
@@ -257,7 +294,7 @@ function LinkPicker({
|
|
|
257
294
|
delay: 250
|
|
258
295
|
}), jsx(VisuallyHidden, {
|
|
259
296
|
id: screenReaderDescriptionId
|
|
260
|
-
}, jsx(FormattedMessage, messages.searchLinkAriaDescription)), jsx(
|
|
297
|
+
}, jsx(FormattedMessage, messages.searchLinkAriaDescription)), jsx(LinkInputField, {
|
|
261
298
|
role: "combobox",
|
|
262
299
|
autoComplete: "off",
|
|
263
300
|
name: "url",
|
|
@@ -277,7 +314,7 @@ function LinkPicker({
|
|
|
277
314
|
onClear: handleClear,
|
|
278
315
|
onKeyDown: handleKeyDown,
|
|
279
316
|
onChange: handleChangeUrl
|
|
280
|
-
}), jsx(
|
|
317
|
+
}), jsx(DisplayTextInputField, {
|
|
281
318
|
autoComplete: "off",
|
|
282
319
|
name: "displayText",
|
|
283
320
|
testId: testIds.textInputField,
|
|
@@ -4,6 +4,8 @@ import _extends from "@babel/runtime/helpers/extends";
|
|
|
4
4
|
import { jsx } from '@emotion/react';
|
|
5
5
|
import { useIntl, FormattedMessage, defineMessages } from 'react-intl-next';
|
|
6
6
|
import Button from '@atlaskit/button';
|
|
7
|
+
import EmptyState from '@atlaskit/empty-state';
|
|
8
|
+
import GenericErrorSVG from '../../../common/generic-error-svg';
|
|
7
9
|
export const CONTACT_SUPPORT_LINK = 'https://support.atlassian.com/contact/';
|
|
8
10
|
export const messages = defineMessages({
|
|
9
11
|
searchErrorHeader: {
|
|
@@ -17,8 +19,6 @@ export const messages = defineMessages({
|
|
|
17
19
|
description: 'Describes possible actions when search throws an error'
|
|
18
20
|
}
|
|
19
21
|
});
|
|
20
|
-
import EmptyState from '@atlaskit/empty-state';
|
|
21
|
-
import GenericErrorSVG from '../../../common/generic-error-svg';
|
|
22
22
|
export const testIds = {
|
|
23
23
|
searchError: 'link-search-error'
|
|
24
24
|
};
|
|
@@ -8,23 +8,21 @@ import Tooltip from '@atlaskit/tooltip';
|
|
|
8
8
|
import Textfield from '@atlaskit/textfield';
|
|
9
9
|
import Selectclear from '@atlaskit/icon/glyph/select-clear';
|
|
10
10
|
import { isRedoEvent, isUndoEvent } from '../utils';
|
|
11
|
-
import { clearTextButtonStyles, fieldStyles } from './styled';
|
|
12
11
|
import { testIds } from '../';
|
|
12
|
+
import { clearTextButtonStyles, fieldStyles } from './styled';
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
...restProps
|
|
27
|
-
} = props;
|
|
14
|
+
const TextInput = ({
|
|
15
|
+
name,
|
|
16
|
+
label,
|
|
17
|
+
autoFocus,
|
|
18
|
+
onRedo,
|
|
19
|
+
onUndo,
|
|
20
|
+
onKeyDown,
|
|
21
|
+
onClear,
|
|
22
|
+
clearLabel,
|
|
23
|
+
error,
|
|
24
|
+
...restProps
|
|
25
|
+
}) => {
|
|
28
26
|
const inputRef = useRef(null);
|
|
29
27
|
const handleRef = useCallback(input => {
|
|
30
28
|
if (input) {
|
|
@@ -38,9 +36,6 @@ const PanelTextInput = props => {
|
|
|
38
36
|
}
|
|
39
37
|
}
|
|
40
38
|
}, [autoFocus]);
|
|
41
|
-
const handleChange = useCallback(e => {
|
|
42
|
-
onChange(e.currentTarget.value);
|
|
43
|
-
}, [onChange]);
|
|
44
39
|
const handleKeydown = useCallback(e => {
|
|
45
40
|
if (typeof onUndo === 'function' && isUndoEvent(e)) {
|
|
46
41
|
e.preventDefault();
|
|
@@ -83,7 +78,6 @@ const PanelTextInput = props => {
|
|
|
83
78
|
}) => {
|
|
84
79
|
return jsx(Fragment, null, jsx(Textfield, _extends({}, fieldProps, restProps, {
|
|
85
80
|
onKeyDown: handleKeydown,
|
|
86
|
-
onChange: handleChange,
|
|
87
81
|
ref: handleRef,
|
|
88
82
|
elemAfterInput: clearText,
|
|
89
83
|
isInvalid: !!error
|
|
@@ -93,4 +87,4 @@ const PanelTextInput = props => {
|
|
|
93
87
|
}));
|
|
94
88
|
};
|
|
95
89
|
|
|
96
|
-
export default
|
|
90
|
+
export default TextInput;
|
package/dist/es2019/version.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Generates analytics utilities from the package analytics spec yaml
|
|
5
5
|
*
|
|
6
|
-
* @codegen <<SignedSource::
|
|
6
|
+
* @codegen <<SignedSource::e4f7029050ef6edde92cac3d9535cd4a>>
|
|
7
7
|
* @codegenCommand yarn workspace @atlaskit/link-picker run codegen-analytics
|
|
8
8
|
*/
|
|
9
9
|
function createEventPayload(eventKey, attributes) {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
3
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
4
|
+
|
|
5
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
6
|
+
|
|
7
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
8
|
+
|
|
9
|
+
import React, { useCallback, useContext, useMemo, useRef, useState } from 'react';
|
|
10
|
+
import { AnalyticsListener } from '@atlaskit/analytics-next';
|
|
11
|
+
import { ANALYTICS_CHANNEL } from '../constants';
|
|
12
|
+
import { normalizeUrl } from '../utils/url';
|
|
13
|
+
var DEFAULT_CONTEXT_ATTRIBUTES = {
|
|
14
|
+
linkState: 'newLink',
|
|
15
|
+
linkFieldContent: null,
|
|
16
|
+
linkFieldContentInputMethod: null,
|
|
17
|
+
displayTextFieldContent: null,
|
|
18
|
+
displayTextFieldContentInputMethod: null
|
|
19
|
+
};
|
|
20
|
+
var LinkPickerAnalyticsContext = /*#__PURE__*/React.createContext({
|
|
21
|
+
trackAttribute: function trackAttribute() {},
|
|
22
|
+
trackAttributes: function trackAttributes() {},
|
|
23
|
+
getAttributes: function getAttributes() {
|
|
24
|
+
return DEFAULT_CONTEXT_ATTRIBUTES;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* TODO: Should we hook this into the form state/reducer?
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
var contextAttributesFromIntialProps = function contextAttributesFromIntialProps(props) {
|
|
32
|
+
return {
|
|
33
|
+
linkState: normalizeUrl(props.url) ? 'editLink' : 'newLink',
|
|
34
|
+
linkFieldContent: normalizeUrl(props.url) ? 'url' : null,
|
|
35
|
+
displayTextFieldContent: Boolean(props.displayText) ? 'text_string' : null
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Context for tracking attributes to deliver at a context-level
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
var LinkPickerAnalytics = function LinkPickerAnalytics(_ref) {
|
|
44
|
+
var initialContext = _ref.initialContext,
|
|
45
|
+
children = _ref.children;
|
|
46
|
+
var dataRef = useRef(_objectSpread(_objectSpread({}, DEFAULT_CONTEXT_ATTRIBUTES), initialContext));
|
|
47
|
+
var methods = useMemo(function () {
|
|
48
|
+
return {
|
|
49
|
+
trackAttribute: function trackAttribute(key, value) {
|
|
50
|
+
dataRef.current[key] = value;
|
|
51
|
+
},
|
|
52
|
+
trackAttributes: function trackAttributes(attributes) {
|
|
53
|
+
dataRef.current = _objectSpread(_objectSpread({}, dataRef.current), attributes);
|
|
54
|
+
},
|
|
55
|
+
getAttributes: function getAttributes() {
|
|
56
|
+
return dataRef.current;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}, []);
|
|
60
|
+
return /*#__PURE__*/React.createElement(LinkPickerAnalyticsContext.Provider, {
|
|
61
|
+
value: methods
|
|
62
|
+
}, children);
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Hook that exposes the context-level attribute getters and setters.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
export var useLinkPickerAnalytics = function useLinkPickerAnalytics() {
|
|
70
|
+
return useContext(LinkPickerAnalyticsContext);
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Wrap component in "attributes" context store and initialise the initial context attributes from props.
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
function withLinkPickerAnalytics(WrappedComponent) {
|
|
77
|
+
return function (props) {
|
|
78
|
+
var _useState = useState(function () {
|
|
79
|
+
return contextAttributesFromIntialProps(props);
|
|
80
|
+
}),
|
|
81
|
+
_useState2 = _slicedToArray(_useState, 1),
|
|
82
|
+
initialContext = _useState2[0];
|
|
83
|
+
|
|
84
|
+
return /*#__PURE__*/React.createElement(LinkPickerAnalytics, _extends({
|
|
85
|
+
initialContext: initialContext
|
|
86
|
+
}, props), /*#__PURE__*/React.createElement(WrappedComponent, props));
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Wraps a component with the analytics context + listener to update events with contextual-level attributes.
|
|
91
|
+
* Should be implemented once at the root of the link picker.
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
export function withLinkPickerAnalyticsContext(WrappedComponent) {
|
|
96
|
+
return withLinkPickerAnalytics(function (props) {
|
|
97
|
+
var _useLinkPickerAnalyti = useLinkPickerAnalytics(),
|
|
98
|
+
getAttributes = _useLinkPickerAnalyti.getAttributes;
|
|
99
|
+
|
|
100
|
+
var onEvent = useCallback(function (event) {
|
|
101
|
+
event.update({
|
|
102
|
+
attributes: getAttributes()
|
|
103
|
+
});
|
|
104
|
+
}, [getAttributes]);
|
|
105
|
+
return /*#__PURE__*/React.createElement(AnalyticsListener, {
|
|
106
|
+
channel: ANALYTICS_CHANNEL,
|
|
107
|
+
onEvent: onEvent
|
|
108
|
+
}, /*#__PURE__*/React.createElement(WrappedComponent, props));
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
3
|
+
import _typeof from "@babel/runtime/helpers/typeof";
|
|
4
|
+
import React, { useCallback, useRef } from 'react';
|
|
5
|
+
import { useAnalyticsEvents } from '@atlaskit/analytics-next';
|
|
6
|
+
import { ANALYTICS_CHANNEL } from '../constants';
|
|
7
|
+
import createEventPayload from '../../analytics.codegen';
|
|
8
|
+
import { useLinkPickerAnalytics } from '.';
|
|
9
|
+
|
|
10
|
+
var isEventWithInputType = function isEventWithInputType(e) {
|
|
11
|
+
return _typeof(e) === 'object' && e !== null && 'inputType' in e;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
var getChangeEventInputMethodType = function getChangeEventInputMethodType(event) {
|
|
15
|
+
if (isEventWithInputType(event.nativeEvent) && typeof event.nativeEvent.inputType === 'string' && event.nativeEvent.inputType.includes('insertFromPaste')) {
|
|
16
|
+
return 'paste';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return 'manual';
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Wraps an input field and connects it to the link picker analytics context to provide a baseline of analytics tracking
|
|
23
|
+
* @param WrappedComponent The input field to be tracked
|
|
24
|
+
* @param field The name of the field (ie matching actionSubjectId)
|
|
25
|
+
* @param filterOnChangeEventAttrs Function to hook into and filter/customise the default attribute tracking logic
|
|
26
|
+
* @returns HOC Input field with tracking
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
export var withInputFieldTracking = function withInputFieldTracking(WrappedComponent, field) {
|
|
31
|
+
var filterOnChangeEventAttrs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function (_, attributes) {
|
|
32
|
+
return attributes;
|
|
33
|
+
};
|
|
34
|
+
return function (props) {
|
|
35
|
+
var onBlur = props.onBlur,
|
|
36
|
+
onChange = props.onChange,
|
|
37
|
+
onClear = props.onClear,
|
|
38
|
+
onFocus = props.onFocus;
|
|
39
|
+
|
|
40
|
+
var _useLinkPickerAnalyti = useLinkPickerAnalytics(),
|
|
41
|
+
trackAttribute = _useLinkPickerAnalyti.trackAttribute,
|
|
42
|
+
trackAttributes = _useLinkPickerAnalyti.trackAttributes;
|
|
43
|
+
|
|
44
|
+
var _useAnalyticsEvents = useAnalyticsEvents(),
|
|
45
|
+
createAnalyticsEvent = _useAnalyticsEvents.createAnalyticsEvent; // Ref to track if the value of the field has changed between focus and blur
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
var valueOnFocus = useRef(null); // Track the content and input method on every direct change to the input
|
|
49
|
+
|
|
50
|
+
var handleOnChange = useCallback(function (event) {
|
|
51
|
+
var _filterOnChangeEventA;
|
|
52
|
+
|
|
53
|
+
// TODO: See above re template literal, improve typesafety here
|
|
54
|
+
// trackAttribute(`${field}FieldContent`, contentTypeFn(event.currentTarget.value));
|
|
55
|
+
// trackAttribute(`${field}FieldContentInputMethod`, methodTypeFn(event));
|
|
56
|
+
trackAttributes(filterOnChangeEventAttrs(event, (_filterOnChangeEventA = {}, _defineProperty(_filterOnChangeEventA, "".concat(field, "FieldContent"), Boolean(event.currentTarget.value) ? 'text_string' : null), _defineProperty(_filterOnChangeEventA, "".concat(field, "FieldContentInputMethod"), getChangeEventInputMethodType(event)), _filterOnChangeEventA)));
|
|
57
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event);
|
|
58
|
+
}, [onChange, trackAttributes]); // Store the value of the field at the time the field is focused so we can know if it changes when the user's focus leaves
|
|
59
|
+
|
|
60
|
+
var handleOnFocus = useCallback(function (event) {
|
|
61
|
+
valueOnFocus.current = event.currentTarget.value;
|
|
62
|
+
onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
|
|
63
|
+
}, [onFocus]); // Fire updated events only when the user's focus leaves the field
|
|
64
|
+
|
|
65
|
+
var handleOnBlur = useCallback(function (event) {
|
|
66
|
+
// Only fire if the value has changed since the field was focused (we are only concerned with the net change to the field)
|
|
67
|
+
if (event.currentTarget.value !== valueOnFocus.current) {
|
|
68
|
+
createAnalyticsEvent(createEventPayload("ui.textField.updated.".concat(field, "Field"), {})).fire(ANALYTICS_CHANNEL);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
|
|
72
|
+
}, [onBlur, createAnalyticsEvent]); // Track the values as null when the text field is cleared
|
|
73
|
+
|
|
74
|
+
var handleOnClear = useCallback(function () {
|
|
75
|
+
trackAttribute("".concat(field, "FieldContent"), null);
|
|
76
|
+
trackAttribute("".concat(field, "FieldContentInputMethod"), null);
|
|
77
|
+
|
|
78
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
79
|
+
args[_key] = arguments[_key];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
onClear === null || onClear === void 0 ? void 0 : onClear.apply(void 0, args);
|
|
83
|
+
}, [onClear, trackAttribute]);
|
|
84
|
+
return /*#__PURE__*/React.createElement(WrappedComponent, _extends({}, props, {
|
|
85
|
+
onBlur: handleOnBlur,
|
|
86
|
+
onChange: handleOnChange,
|
|
87
|
+
onClear: handleOnClear,
|
|
88
|
+
onFocus: handleOnFocus
|
|
89
|
+
}));
|
|
90
|
+
};
|
|
91
|
+
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export var ANALYTICS_CHANNEL = 'media';
|
|
2
|
-
export var COMPONENT_NAME = '
|
|
2
|
+
export var COMPONENT_NAME = 'linkPicker';
|
package/dist/esm/ui/index.js
CHANGED
|
@@ -8,7 +8,8 @@ import { LoaderFallback } from './loader-fallback';
|
|
|
8
8
|
export var PACKAGE_DATA = {
|
|
9
9
|
packageName: packageName,
|
|
10
10
|
packageVersion: packageVersion,
|
|
11
|
-
componentName: COMPONENT_NAME
|
|
11
|
+
componentName: COMPONENT_NAME,
|
|
12
|
+
source: COMPONENT_NAME
|
|
12
13
|
};
|
|
13
14
|
var LazyLinkPicker = lazyForPaint(function () {
|
|
14
15
|
return import(
|