@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.
Files changed (42) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/cjs/analytics.codegen.js +1 -1
  3. package/dist/cjs/common/analytics/context.js +137 -0
  4. package/dist/cjs/common/analytics/index.js +27 -0
  5. package/dist/cjs/common/analytics/input-field-tracking.js +115 -0
  6. package/dist/cjs/common/constants.js +1 -1
  7. package/dist/cjs/ui/index.js +2 -1
  8. package/dist/cjs/ui/link-picker/index.js +56 -16
  9. package/dist/cjs/ui/link-picker/text-input/index.js +18 -23
  10. package/dist/cjs/version.json +1 -1
  11. package/dist/es2019/analytics.codegen.js +1 -1
  12. package/dist/es2019/common/analytics/context.js +95 -0
  13. package/dist/es2019/common/analytics/index.js +2 -0
  14. package/dist/es2019/common/analytics/input-field-tracking.js +85 -0
  15. package/dist/es2019/common/constants.js +1 -1
  16. package/dist/es2019/ui/index.js +2 -1
  17. package/dist/es2019/ui/link-picker/index.js +51 -14
  18. package/dist/es2019/ui/link-picker/link-search-error/index.js +2 -2
  19. package/dist/es2019/ui/link-picker/text-input/index.js +14 -20
  20. package/dist/es2019/version.json +1 -1
  21. package/dist/esm/analytics.codegen.js +1 -1
  22. package/dist/esm/common/analytics/context.js +110 -0
  23. package/dist/esm/common/analytics/index.js +2 -0
  24. package/dist/esm/common/analytics/input-field-tracking.js +91 -0
  25. package/dist/esm/common/constants.js +1 -1
  26. package/dist/esm/ui/index.js +2 -1
  27. package/dist/esm/ui/link-picker/index.js +54 -14
  28. package/dist/esm/ui/link-picker/link-search-error/index.js +2 -2
  29. package/dist/esm/ui/link-picker/text-input/index.js +16 -21
  30. package/dist/esm/version.json +1 -1
  31. package/dist/types/analytics.codegen.d.ts +9 -1
  32. package/dist/types/common/analytics/context.d.ts +28 -0
  33. package/dist/types/common/analytics/index.d.ts +2 -0
  34. package/dist/types/common/analytics/input-field-tracking.d.ts +17 -0
  35. package/dist/types/common/constants.d.ts +1 -1
  36. package/dist/types/ui/index.d.ts +1 -0
  37. package/dist/types/ui/link-picker/text-input/index.d.ts +6 -8
  38. package/package.json +1 -1
  39. package/dist/cjs/services/with-link-picker-analytics/index.js +0 -33
  40. package/dist/es2019/services/with-link-picker-analytics/index.js +0 -15
  41. package/dist/esm/services/with-link-picker-analytics/index.js +0 -17
  42. package/dist/types/services/with-link-picker-analytics/index.d.ts +0 -4
@@ -0,0 +1,2 @@
1
+ export { useLinkPickerAnalytics, withLinkPickerAnalyticsContext } from './context';
2
+ export { withInputFieldTracking } from './input-field-tracking';
@@ -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 = 'LinkPicker';
2
+ export const COMPONENT_NAME = 'linkPicker';
@@ -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(url => {
141
+ const handleChangeUrl = useCallback(e => {
121
142
  dispatch({
122
- url
143
+ url: e.currentTarget.value
123
144
  });
124
145
  }, []);
125
- const handleChangeText = useCallback(displayText => {
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(); // Dispatch the original event to our channel
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(PanelTextInput, {
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(PanelTextInput, {
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 PanelTextInput = props => {
15
- const {
16
- name,
17
- label,
18
- autoFocus,
19
- onRedo,
20
- onUndo,
21
- onChange,
22
- onKeyDown,
23
- onClear,
24
- clearLabel,
25
- error,
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 PanelTextInput;
90
+ export default TextInput;
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/link-picker",
3
- "version": "1.9.2",
3
+ "version": "1.9.3",
4
4
  "sideEffects": false
5
5
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Generates analytics utilities from the package analytics spec yaml
5
5
  *
6
- * @codegen <<SignedSource::115bafba068f36f76da67200ee2d08c4>>
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,2 @@
1
+ export { useLinkPickerAnalytics, withLinkPickerAnalyticsContext } from './context';
2
+ export { withInputFieldTracking } from './input-field-tracking';
@@ -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 = 'LinkPicker';
2
+ export var COMPONENT_NAME = 'linkPicker';
@@ -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(