@atlaskit/smart-card 44.3.0 → 44.3.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 (49) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/analytics.spec.yaml +9 -0
  3. package/dist/cjs/ssr.js +28 -1
  4. package/dist/cjs/state/hooks/use-incoming-outgoing-links/index.js +8 -14
  5. package/dist/cjs/state/hooks/use-intersection-observer/index.js +49 -0
  6. package/dist/cjs/utils/analytics/analytics.js +1 -1
  7. package/dist/cjs/view/CardWithUrl/card-error-boundary/index.js +28 -0
  8. package/dist/cjs/view/CardWithUrl/card-intersection-observer/index.js +46 -0
  9. package/dist/cjs/view/CardWithUrl/card-loader-wrapper/index.js +23 -0
  10. package/dist/cjs/view/CardWithUrl/component-lazy/LazyIntersectionObserverCard.js +43 -3
  11. package/dist/cjs/view/CardWithUrl/component.js +34 -2
  12. package/dist/cjs/view/LinkUrl/index.js +1 -1
  13. package/dist/es2019/ssr.js +28 -2
  14. package/dist/es2019/state/hooks/use-incoming-outgoing-links/index.js +8 -12
  15. package/dist/es2019/state/hooks/use-intersection-observer/index.js +41 -0
  16. package/dist/es2019/utils/analytics/analytics.js +1 -1
  17. package/dist/es2019/view/CardWithUrl/card-error-boundary/index.js +20 -0
  18. package/dist/es2019/view/CardWithUrl/card-intersection-observer/index.js +25 -0
  19. package/dist/es2019/view/CardWithUrl/card-loader-wrapper/index.js +16 -0
  20. package/dist/es2019/view/CardWithUrl/component-lazy/LazyIntersectionObserverCard.js +39 -2
  21. package/dist/es2019/view/CardWithUrl/component.js +34 -2
  22. package/dist/es2019/view/LinkUrl/index.js +1 -1
  23. package/dist/esm/ssr.js +29 -2
  24. package/dist/esm/state/hooks/use-incoming-outgoing-links/index.js +8 -14
  25. package/dist/esm/state/hooks/use-intersection-observer/index.js +44 -0
  26. package/dist/esm/utils/analytics/analytics.js +1 -1
  27. package/dist/esm/view/CardWithUrl/card-error-boundary/index.js +21 -0
  28. package/dist/esm/view/CardWithUrl/card-intersection-observer/index.js +37 -0
  29. package/dist/esm/view/CardWithUrl/card-loader-wrapper/index.js +15 -0
  30. package/dist/esm/view/CardWithUrl/component-lazy/LazyIntersectionObserverCard.js +42 -2
  31. package/dist/esm/view/CardWithUrl/component.js +34 -2
  32. package/dist/esm/view/LinkUrl/index.js +1 -1
  33. package/dist/types/common/analytics/generated/analytics.types.d.ts +7 -1
  34. package/dist/types/state/hooks/use-intersection-observer/index.d.ts +6 -0
  35. package/dist/types/view/CardWithUrl/card-error-boundary/index.d.ts +5 -0
  36. package/dist/types/view/CardWithUrl/card-intersection-observer/index.d.ts +7 -0
  37. package/dist/types/view/CardWithUrl/card-loader-wrapper/index.d.ts +7 -0
  38. package/dist/types/view/CardWithUrl/component-lazy/LazyIntersectionObserverCard.d.ts +1 -1
  39. package/dist/types/view/CardWithUrl/component.d.ts +1 -0
  40. package/dist/types/view/CardWithUrl/types.d.ts +1 -0
  41. package/dist/types-ts4.5/common/analytics/generated/analytics.types.d.ts +7 -1
  42. package/dist/types-ts4.5/state/hooks/use-intersection-observer/index.d.ts +6 -0
  43. package/dist/types-ts4.5/view/CardWithUrl/card-error-boundary/index.d.ts +5 -0
  44. package/dist/types-ts4.5/view/CardWithUrl/card-intersection-observer/index.d.ts +7 -0
  45. package/dist/types-ts4.5/view/CardWithUrl/card-loader-wrapper/index.d.ts +7 -0
  46. package/dist/types-ts4.5/view/CardWithUrl/component-lazy/LazyIntersectionObserverCard.d.ts +1 -1
  47. package/dist/types-ts4.5/view/CardWithUrl/component.d.ts +1 -0
  48. package/dist/types-ts4.5/view/CardWithUrl/types.d.ts +1 -0
  49. package/package.json +4 -4
@@ -0,0 +1,25 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ import React, { useCallback, useState } from 'react';
3
+ import useIntersectionObserver from '../../../state/hooks/use-intersection-observer';
4
+ import { isIntersectionObserverSupported } from '../../../utils';
5
+ import CardLoaderWrapper from '../card-loader-wrapper';
6
+ const withCardIntersectionObserver = Component => props => {
7
+ // TODO: NAVX-4682: Add feature parity to LazyIntersectionObserverCard for no lazy loading
8
+ const [isIntersected, setIsIntersected] = useState(false);
9
+ const onIntersecting = useCallback(() => {
10
+ setIsIntersected(true);
11
+ }, []);
12
+ const ref = useIntersectionObserver({
13
+ onIntersecting
14
+ });
15
+ return /*#__PURE__*/React.createElement(CardLoaderWrapper, {
16
+ appearance: props.appearance,
17
+ ref: ref
18
+ }, /*#__PURE__*/React.createElement(Component, _extends({}, props, {
19
+ isIntersected: isIntersected
20
+ })));
21
+ };
22
+ const withCardIntersectionObserverFallback = Component => props => /*#__PURE__*/React.createElement(CardLoaderWrapper, {
23
+ appearance: props.appearance
24
+ }, /*#__PURE__*/React.createElement(Component, props));
25
+ export default (Component => isIntersectionObserverSupported() ? withCardIntersectionObserver(Component) : withCardIntersectionObserverFallback(Component));
@@ -0,0 +1,16 @@
1
+ import React, { forwardRef } from 'react';
2
+ const CardLoaderWrapper = /*#__PURE__*/forwardRef(({
3
+ appearance,
4
+ children
5
+ }, ref) => {
6
+ const Component = appearance === 'inline' ? 'span' : 'div';
7
+ return (
8
+ /*#__PURE__*/
9
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
10
+ React.createElement(Component, {
11
+ className: "loader-wrapper",
12
+ ref: ref
13
+ }, children)
14
+ );
15
+ });
16
+ export default CardLoaderWrapper;
@@ -1,7 +1,11 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
1
2
  import React, { useCallback, useEffect, useRef, useState } from 'react';
3
+ import { componentWithFG } from '@atlaskit/platform-feature-flags-react';
2
4
  import { usePrefetch } from '../../../state';
3
5
  import { startUfoExperience } from '../../../state/analytics/ufoExperiences';
6
+ import useIntersectionObserver from '../../../state/hooks/use-intersection-observer';
4
7
  import { shouldSample } from '../../../utils/shouldSample';
8
+ import CardLoaderWrapper from '../card-loader-wrapper';
5
9
  import { CardWithUrlContent } from '../component';
6
10
  import { LoadingCardLink } from './LoadingCardLink';
7
11
 
@@ -10,7 +14,7 @@ import { LoadingCardLink } from './LoadingCardLink';
10
14
  // being compared to. Since the default container is the `document`, we set this
11
15
  // up to check once a Smart Link is within `X` px from the bottom of the viewport.
12
16
  const ROOT_MARGIN_VERTICAL = '360px';
13
- export function LazyIntersectionObserverCard(props) {
17
+ function LazyIntersectionObserverCardOld(props) {
14
18
  const ref = useRef(null);
15
19
  const [isIntersecting, setIsIntersecting] = useState(false);
16
20
  const [shouldSendRenderedUFOEvent] = useState(shouldSample());
@@ -52,4 +56,37 @@ export function LazyIntersectionObserverCard(props) {
52
56
  return /*#__PURE__*/React.createElement(Component, {
53
57
  className: "loader-wrapper"
54
58
  }, content);
55
- }
59
+ }
60
+ const LazyIntersectionObserverCardNew = props => {
61
+ const [isIntersected, setIsIntersected] = useState(false);
62
+ const [shouldSendRenderedUFOEvent] = useState(shouldSample());
63
+ const {
64
+ appearance,
65
+ url,
66
+ id
67
+ } = props;
68
+ const prefetch = usePrefetch(url);
69
+ const ComponentObserver = appearance === 'inline' ? 'span' : 'div';
70
+ const onIntersection = useCallback(isIntersecting => {
71
+ if (isIntersecting) {
72
+ if (shouldSendRenderedUFOEvent) {
73
+ startUfoExperience('smart-link-rendered', id);
74
+ }
75
+ setIsIntersected(true);
76
+ } else {
77
+ prefetch();
78
+ }
79
+ }, [id, prefetch, shouldSendRenderedUFOEvent]);
80
+ const ref = useIntersectionObserver({
81
+ onIntersection
82
+ });
83
+ const content = isIntersected ? /*#__PURE__*/React.createElement(CardWithUrlContent, _extends({}, props, {
84
+ isIntersected: true
85
+ })) : /*#__PURE__*/React.createElement(ComponentObserver, {
86
+ ref: ref
87
+ }, /*#__PURE__*/React.createElement(LoadingCardLink, props));
88
+ return /*#__PURE__*/React.createElement(CardLoaderWrapper, {
89
+ appearance: appearance
90
+ }, content);
91
+ };
92
+ export const LazyIntersectionObserverCard = componentWithFG('platform_sl_event_ui_seen', LazyIntersectionObserverCardNew, LazyIntersectionObserverCardOld);
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useMemo } from 'react';
1
+ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
2
2
  import { useAnalyticsEvents as useAnalyticsEventsNext } from '@atlaskit/analytics-next';
3
3
  import { extractSmartLinkEmbed } from '@atlaskit/link-extractors';
4
4
  import { fg } from '@atlaskit/platform-feature-flags';
@@ -22,6 +22,7 @@ import { EmbedCard, EmbedCardUpdated } from '../EmbedCard';
22
22
  import FlexibleCard from '../FlexibleCard';
23
23
  import { InlineCard } from '../InlineCard';
24
24
  import { useFire3PWorkflowsClickEvent } from '../SmartLinkEvents/useSmartLinkEvents';
25
+ import withCardIntersectionObserver from './card-intersection-observer';
25
26
  const thirdPartyARIPrefix = 'ari:third-party';
26
27
  const EmbedCardComponent = componentWithFG('rovo_chat_embed_card_dwell_and_hover_metrics', EmbedCardUpdated, EmbedCard);
27
28
  function Component({
@@ -29,6 +30,7 @@ function Component({
29
30
  url,
30
31
  isSelected,
31
32
  isHovered,
33
+ isIntersected,
32
34
  frameStyle,
33
35
  platform,
34
36
  onClick,
@@ -59,6 +61,7 @@ function Component({
59
61
  const {
60
62
  fireEvent
61
63
  } = useAnalyticsEvents();
64
+ const hasFiredSeenRef = useRef(false);
62
65
  let isFlexibleUi = useMemo(() => isFlexibleUiCard(children, ui), [children, ui]);
63
66
 
64
67
  // Get state, actions for this card.
@@ -207,6 +210,19 @@ function Component({
207
210
  });
208
211
  }
209
212
  }, [appearance, extensionKey, fireEvent, id, isFlexibleUi, state.status]);
213
+
214
+ // Fire smartLink seen event once on a successful render card is in the viewport
215
+ useEffect(() => {
216
+ if (fg('platform_sl_event_ui_seen')) {
217
+ if (!isFinalState(state.status)) return;
218
+ if (isIntersected && !hasFiredSeenRef.current) {
219
+ fireEvent('ui.smartLink.seen', {
220
+ display: appearance
221
+ });
222
+ hasFiredSeenRef.current = true;
223
+ }
224
+ }
225
+ }, [state.status, isIntersected, fireEvent, appearance]);
210
226
  const onIframeDwell = useCallback((dwellTime, dwellPercentVisible) => {
211
227
  fireEvent('ui.smartLinkIframe.dwelled', {
212
228
  id,
@@ -329,6 +345,7 @@ function ComponentUpdated({
329
345
  url,
330
346
  isSelected,
331
347
  isHovered,
348
+ isIntersected,
332
349
  frameStyle,
333
350
  platform,
334
351
  onClick,
@@ -359,6 +376,7 @@ function ComponentUpdated({
359
376
  const {
360
377
  fireEvent
361
378
  } = useAnalyticsEvents();
379
+ const hasFiredSeenRef = useRef(false);
362
380
  let isFlexibleUi = useMemo(() => isFlexibleUiCard(children, ui), [children, ui]);
363
381
 
364
382
  // Get state, actions for this card.
@@ -507,6 +525,19 @@ function ComponentUpdated({
507
525
  });
508
526
  }
509
527
  }, [appearance, extensionKey, fireEvent, id, isFlexibleUi, state.status]);
528
+
529
+ // Fire smartLink seen event once on a successful render card is in the viewport
530
+ useEffect(() => {
531
+ if (fg('platform_sl_event_ui_seen')) {
532
+ if (!isFinalState(state.status)) return;
533
+ if (isIntersected && !hasFiredSeenRef.current) {
534
+ fireEvent('ui.smartLink.seen', {
535
+ display: appearance
536
+ });
537
+ hasFiredSeenRef.current = true;
538
+ }
539
+ }
540
+ }, [state.status, isIntersected, fireEvent, appearance]);
510
541
  const onIframeDwell = useCallback((dwellTime, dwellPercentVisible) => {
511
542
  fireEvent('ui.smartLinkIframe.dwelled', {
512
543
  id,
@@ -651,4 +682,5 @@ export const CardWithUrlContent = props => {
651
682
  id: props.id,
652
683
  display: display
653
684
  }, /*#__PURE__*/React.createElement(CardWithUrlContentComponent, props)));
654
- };
685
+ };
686
+ export const CardWithUrl = withCardIntersectionObserver(CardWithUrlContent);
@@ -12,7 +12,7 @@ import LinkWarningModal from './LinkWarningModal';
12
12
  import { useLinkWarningModal } from './LinkWarningModal/hooks/use-link-warning-modal';
13
13
  const PACKAGE_DATA = {
14
14
  packageName: "@atlaskit/smart-card",
15
- packageVersion: "44.2.0",
15
+ packageVersion: "44.3.1",
16
16
  componentName: 'linkUrl'
17
17
  };
18
18
  const Anchor = withLinkClickedEvent('a');
package/dist/esm/ssr.js CHANGED
@@ -7,13 +7,15 @@ import { ErrorBoundary } from 'react-error-boundary';
7
7
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
8
8
  import uuid from 'uuid';
9
9
  import { AnalyticsContext } from '@atlaskit/analytics-next';
10
+ import { fg } from '@atlaskit/platform-feature-flags';
10
11
  import { context } from './utils/analytics/analytics';
11
- import { CardWithUrlContent } from './view/CardWithUrl/component';
12
+ import CardErrorBoundary from './view/CardWithUrl/card-error-boundary';
13
+ import { CardWithUrl, CardWithUrlContent } from './view/CardWithUrl/component';
12
14
  import { LoadingCardLink } from './view/CardWithUrl/component-lazy/LoadingCardLink';
13
15
  // SSR friendly version of smart-card
14
16
  // simplifies the logic around rendering and loading placeholders and
15
17
  // only contains whats necessary to render the card on SSR mode
16
- export var CardSSR = function CardSSR(props) {
18
+ var CardSSROld = function CardSSROld(props) {
17
19
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
18
20
  var _useState = useState(function () {
19
21
  var _props$id;
@@ -39,4 +41,29 @@ export var CardSSR = function CardSSR(props) {
39
41
  }, /*#__PURE__*/React.createElement(Component, {
40
42
  className: "loader-wrapper"
41
43
  }, /*#__PURE__*/React.createElement(CardWithUrlContent, cardProps))));
44
+ };
45
+
46
+ // SSR friendly version of smart-card
47
+ // simplifies the logic around rendering and loading placeholders and
48
+ // only contains whats necessary to render the card on SSR mode
49
+ var CardSSRNew = function CardSSRNew(props) {
50
+ // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
51
+ var _useState3 = useState(function () {
52
+ var _props$id2;
53
+ return (_props$id2 = props.id) !== null && _props$id2 !== void 0 ? _props$id2 : uuid();
54
+ }),
55
+ _useState4 = _slicedToArray(_useState3, 1),
56
+ id = _useState4[0];
57
+ var propsWithId = _objectSpread(_objectSpread({}, props), {}, {
58
+ id: id
59
+ });
60
+ return /*#__PURE__*/React.createElement(AnalyticsContext, {
61
+ data: context
62
+ }, /*#__PURE__*/React.createElement(CardErrorBoundary, propsWithId, /*#__PURE__*/React.createElement(CardWithUrl, propsWithId)));
63
+ };
64
+ export var CardSSR = function CardSSR(props) {
65
+ if (fg('platform_sl_event_ui_seen')) {
66
+ return /*#__PURE__*/React.createElement(CardSSRNew, props);
67
+ }
68
+ return /*#__PURE__*/React.createElement(CardSSROld, props);
42
69
  };
@@ -2,7 +2,6 @@ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
2
  import _regeneratorRuntime from "@babel/runtime/regenerator";
3
3
  import { useCallback, useMemo } from 'react';
4
4
  import { request } from '@atlaskit/linking-common';
5
- import { fg } from '@atlaskit/platform-feature-flags';
6
5
  import { queryIncomingOutgoingLinks as queryIncomingOutgoingAris } from './query';
7
6
  /**
8
7
  * @param baseUriWithNoTrailingSlash base url which will then be appended with /gateway/api/graphql to make requests to AGG
@@ -100,8 +99,8 @@ var useIncomingOutgoingAri = function useIncomingOutgoingAri() {
100
99
  var _response$data$graphS, _response$data, _response$data$graphS2, _response$data2;
101
100
  var firstIncoming,
102
101
  firstOutgoing,
103
- headers,
104
102
  siteId,
103
+ headers,
105
104
  response,
106
105
  incomingAris,
107
106
  outgoingAris,
@@ -111,28 +110,23 @@ var useIncomingOutgoingAri = function useIncomingOutgoingAri() {
111
110
  case 0:
112
111
  firstIncoming = _args4.length > 1 && _args4[1] !== undefined ? _args4[1] : 50;
113
112
  firstOutgoing = _args4.length > 2 && _args4[2] !== undefined ? _args4[2] : 50;
114
- if (!fg('platform_navx_send_context_to_ugs_for_rel_links')) {
115
- _context4.next = 9;
116
- break;
117
- }
118
- _context4.next = 5;
113
+ _context4.next = 4;
119
114
  return getSiteId(ari);
120
- case 5:
115
+ case 4:
121
116
  siteId = _context4.sent;
122
117
  if (siteId) {
123
- _context4.next = 8;
118
+ _context4.next = 7;
124
119
  break;
125
120
  }
126
121
  return _context4.abrupt("return", {
127
122
  incomingAris: [],
128
123
  outgoingAris: []
129
124
  });
130
- case 8:
125
+ case 7:
131
126
  headers = {
132
127
  'X-Query-Context': "ari:cloud:platform::site/".concat(siteId)
133
128
  };
134
- case 9:
135
- _context4.next = 11;
129
+ _context4.next = 10;
136
130
  return aggRequestCall({
137
131
  variables: {
138
132
  id: ari,
@@ -141,7 +135,7 @@ var useIncomingOutgoingAri = function useIncomingOutgoingAri() {
141
135
  },
142
136
  query: queryIncomingOutgoingAris
143
137
  }, headers);
144
- case 11:
138
+ case 10:
145
139
  response = _context4.sent;
146
140
  incomingAris = (_response$data$graphS = response === null || response === void 0 || (_response$data = response.data) === null || _response$data === void 0 || (_response$data = _response$data.graphStore) === null || _response$data === void 0 || (_response$data = _response$data.incoming) === null || _response$data === void 0 || (_response$data = _response$data.aris) === null || _response$data === void 0 || (_response$data = _response$data.map(function (node) {
147
141
  return node === null || node === void 0 ? void 0 : node.id;
@@ -157,7 +151,7 @@ var useIncomingOutgoingAri = function useIncomingOutgoingAri() {
157
151
  incomingAris: incomingAris,
158
152
  outgoingAris: outgoingAris
159
153
  });
160
- case 15:
154
+ case 14:
161
155
  case "end":
162
156
  return _context4.stop();
163
157
  }
@@ -0,0 +1,44 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+
3
+ // This property enables the intersection observer to be run once the
4
+ // HTML element being observed is within `X` px of the target container it is
5
+ // being compared to. Since the default container is the `document`, we set this
6
+ // up to check once a Smart Link is within `X` px from the bottom of the viewport.
7
+ var ROOT_MARGIN_VERTICAL = '360px';
8
+ var useIntersectionObserver = function useIntersectionObserver(_ref) {
9
+ var onIntersectingCallback = _ref.onIntersecting,
10
+ onIntersectionCallback = _ref.onIntersection;
11
+ var ref = useRef(null);
12
+ var onIntersectingCallbackRef = useRef(onIntersectingCallback);
13
+ var onIntersectionCallbackRef = useRef(onIntersectionCallback);
14
+ useEffect(function () {
15
+ onIntersectingCallbackRef.current = onIntersectingCallback;
16
+ onIntersectionCallbackRef.current = onIntersectionCallback;
17
+ }, [onIntersectingCallback, onIntersectionCallback]);
18
+ var onIntersection = useCallback(function (entries, observer) {
19
+ var _onIntersectionCallba;
20
+ var isIntersecting = entries.some(function (entry) {
21
+ return entry.isIntersecting;
22
+ });
23
+ (_onIntersectionCallba = onIntersectionCallbackRef.current) === null || _onIntersectionCallba === void 0 || _onIntersectionCallba.call(onIntersectionCallbackRef, isIntersecting);
24
+ if (isIntersecting) {
25
+ var _onIntersectingCallba;
26
+ (_onIntersectingCallba = onIntersectingCallbackRef.current) === null || _onIntersectingCallba === void 0 || _onIntersectingCallba.call(onIntersectingCallbackRef);
27
+ observer.disconnect();
28
+ }
29
+ }, []);
30
+ useEffect(function () {
31
+ if (!ref.current) {
32
+ return;
33
+ }
34
+ var intersectionObserver = new IntersectionObserver(onIntersection, {
35
+ rootMargin: "".concat(ROOT_MARGIN_VERTICAL, " 0px ").concat(ROOT_MARGIN_VERTICAL, " 0px")
36
+ });
37
+ intersectionObserver.observe(ref.current);
38
+ return function () {
39
+ return intersectionObserver.disconnect();
40
+ };
41
+ }, [ref, onIntersection]);
42
+ return ref;
43
+ };
44
+ export default useIntersectionObserver;
@@ -4,7 +4,7 @@ export var ANALYTICS_CHANNEL = 'media';
4
4
  export var context = {
5
5
  componentName: 'smart-cards',
6
6
  packageName: "@atlaskit/smart-card",
7
- packageVersion: "44.2.0"
7
+ packageVersion: "44.3.1"
8
8
  };
9
9
  export var TrackQuickActionType = /*#__PURE__*/function (TrackQuickActionType) {
10
10
  TrackQuickActionType["StatusUpdate"] = "StatusUpdate";
@@ -0,0 +1,21 @@
1
+ import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
2
+ var _excluded = ["children"];
3
+ import React from 'react';
4
+ import { ErrorBoundary } from 'react-error-boundary';
5
+ import { LoadingCardLink } from '../component-lazy/LoadingCardLink';
6
+ var CardErrorBoundary = function CardErrorBoundary(_ref) {
7
+ var children = _ref.children,
8
+ props = _objectWithoutProperties(_ref, _excluded);
9
+ // TODO: NAVX-4682: Add feature parity to CardWithURLRenderer
10
+ var ErrorFallbackComponent = props.fallbackComponent;
11
+ var errorBoundaryFallbackComponent = function errorBoundaryFallbackComponent() {
12
+ if (ErrorFallbackComponent) {
13
+ return /*#__PURE__*/React.createElement(ErrorFallbackComponent, null);
14
+ }
15
+ return /*#__PURE__*/React.createElement(LoadingCardLink, props);
16
+ };
17
+ return /*#__PURE__*/React.createElement(ErrorBoundary, {
18
+ FallbackComponent: errorBoundaryFallbackComponent
19
+ }, children);
20
+ };
21
+ export default CardErrorBoundary;
@@ -0,0 +1,37 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
+ import React, { useCallback, useState } from 'react';
4
+ import useIntersectionObserver from '../../../state/hooks/use-intersection-observer';
5
+ import { isIntersectionObserverSupported } from '../../../utils';
6
+ import CardLoaderWrapper from '../card-loader-wrapper';
7
+ var withCardIntersectionObserver = function withCardIntersectionObserver(Component) {
8
+ return function (props) {
9
+ // TODO: NAVX-4682: Add feature parity to LazyIntersectionObserverCard for no lazy loading
10
+ var _useState = useState(false),
11
+ _useState2 = _slicedToArray(_useState, 2),
12
+ isIntersected = _useState2[0],
13
+ setIsIntersected = _useState2[1];
14
+ var onIntersecting = useCallback(function () {
15
+ setIsIntersected(true);
16
+ }, []);
17
+ var ref = useIntersectionObserver({
18
+ onIntersecting: onIntersecting
19
+ });
20
+ return /*#__PURE__*/React.createElement(CardLoaderWrapper, {
21
+ appearance: props.appearance,
22
+ ref: ref
23
+ }, /*#__PURE__*/React.createElement(Component, _extends({}, props, {
24
+ isIntersected: isIntersected
25
+ })));
26
+ };
27
+ };
28
+ var withCardIntersectionObserverFallback = function withCardIntersectionObserverFallback(Component) {
29
+ return function (props) {
30
+ return /*#__PURE__*/React.createElement(CardLoaderWrapper, {
31
+ appearance: props.appearance
32
+ }, /*#__PURE__*/React.createElement(Component, props));
33
+ };
34
+ };
35
+ export default (function (Component) {
36
+ return isIntersectionObserverSupported() ? withCardIntersectionObserver(Component) : withCardIntersectionObserverFallback(Component);
37
+ });
@@ -0,0 +1,15 @@
1
+ import React, { forwardRef } from 'react';
2
+ var CardLoaderWrapper = /*#__PURE__*/forwardRef(function (_ref, ref) {
3
+ var appearance = _ref.appearance,
4
+ children = _ref.children;
5
+ var Component = appearance === 'inline' ? 'span' : 'div';
6
+ return (
7
+ /*#__PURE__*/
8
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
9
+ React.createElement(Component, {
10
+ className: "loader-wrapper",
11
+ ref: ref
12
+ }, children)
13
+ );
14
+ });
15
+ export default CardLoaderWrapper;
@@ -1,8 +1,12 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
1
2
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
3
  import React, { useCallback, useEffect, useRef, useState } from 'react';
4
+ import { componentWithFG } from '@atlaskit/platform-feature-flags-react';
3
5
  import { usePrefetch } from '../../../state';
4
6
  import { startUfoExperience } from '../../../state/analytics/ufoExperiences';
7
+ import useIntersectionObserver from '../../../state/hooks/use-intersection-observer';
5
8
  import { shouldSample } from '../../../utils/shouldSample';
9
+ import CardLoaderWrapper from '../card-loader-wrapper';
6
10
  import { CardWithUrlContent } from '../component';
7
11
  import { LoadingCardLink } from './LoadingCardLink';
8
12
 
@@ -11,7 +15,7 @@ import { LoadingCardLink } from './LoadingCardLink';
11
15
  // being compared to. Since the default container is the `document`, we set this
12
16
  // up to check once a Smart Link is within `X` px from the bottom of the viewport.
13
17
  var ROOT_MARGIN_VERTICAL = '360px';
14
- export function LazyIntersectionObserverCard(props) {
18
+ function LazyIntersectionObserverCardOld(props) {
15
19
  var ref = useRef(null);
16
20
  var _useState = useState(false),
17
21
  _useState2 = _slicedToArray(_useState, 2),
@@ -60,4 +64,40 @@ export function LazyIntersectionObserverCard(props) {
60
64
  return /*#__PURE__*/React.createElement(Component, {
61
65
  className: "loader-wrapper"
62
66
  }, content);
63
- }
67
+ }
68
+ var LazyIntersectionObserverCardNew = function LazyIntersectionObserverCardNew(props) {
69
+ var _useState5 = useState(false),
70
+ _useState6 = _slicedToArray(_useState5, 2),
71
+ isIntersected = _useState6[0],
72
+ setIsIntersected = _useState6[1];
73
+ var _useState7 = useState(shouldSample()),
74
+ _useState8 = _slicedToArray(_useState7, 1),
75
+ shouldSendRenderedUFOEvent = _useState8[0];
76
+ var appearance = props.appearance,
77
+ url = props.url,
78
+ id = props.id;
79
+ var prefetch = usePrefetch(url);
80
+ var ComponentObserver = appearance === 'inline' ? 'span' : 'div';
81
+ var onIntersection = useCallback(function (isIntersecting) {
82
+ if (isIntersecting) {
83
+ if (shouldSendRenderedUFOEvent) {
84
+ startUfoExperience('smart-link-rendered', id);
85
+ }
86
+ setIsIntersected(true);
87
+ } else {
88
+ prefetch();
89
+ }
90
+ }, [id, prefetch, shouldSendRenderedUFOEvent]);
91
+ var ref = useIntersectionObserver({
92
+ onIntersection: onIntersection
93
+ });
94
+ var content = isIntersected ? /*#__PURE__*/React.createElement(CardWithUrlContent, _extends({}, props, {
95
+ isIntersected: true
96
+ })) : /*#__PURE__*/React.createElement(ComponentObserver, {
97
+ ref: ref
98
+ }, /*#__PURE__*/React.createElement(LoadingCardLink, props));
99
+ return /*#__PURE__*/React.createElement(CardLoaderWrapper, {
100
+ appearance: appearance
101
+ }, content);
102
+ };
103
+ export var LazyIntersectionObserverCard = componentWithFG('platform_sl_event_ui_seen', LazyIntersectionObserverCardNew, LazyIntersectionObserverCardOld);
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useMemo } from 'react';
1
+ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
2
2
  import { useAnalyticsEvents as useAnalyticsEventsNext } from '@atlaskit/analytics-next';
3
3
  import { extractSmartLinkEmbed } from '@atlaskit/link-extractors';
4
4
  import { fg } from '@atlaskit/platform-feature-flags';
@@ -22,6 +22,7 @@ import { EmbedCard, EmbedCardUpdated } from '../EmbedCard';
22
22
  import FlexibleCard from '../FlexibleCard';
23
23
  import { InlineCard } from '../InlineCard';
24
24
  import { useFire3PWorkflowsClickEvent } from '../SmartLinkEvents/useSmartLinkEvents';
25
+ import withCardIntersectionObserver from './card-intersection-observer';
25
26
  var thirdPartyARIPrefix = 'ari:third-party';
26
27
  var EmbedCardComponent = componentWithFG('rovo_chat_embed_card_dwell_and_hover_metrics', EmbedCardUpdated, EmbedCard);
27
28
  function Component(_ref) {
@@ -29,6 +30,7 @@ function Component(_ref) {
29
30
  url = _ref.url,
30
31
  isSelected = _ref.isSelected,
31
32
  isHovered = _ref.isHovered,
33
+ isIntersected = _ref.isIntersected,
32
34
  frameStyle = _ref.frameStyle,
33
35
  platform = _ref.platform,
34
36
  onClick = _ref.onClick,
@@ -56,6 +58,7 @@ function Component(_ref) {
56
58
  createAnalyticsEvent = _useAnalyticsEventsNe.createAnalyticsEvent;
57
59
  var _useAnalyticsEvents = useAnalyticsEvents(),
58
60
  fireEvent = _useAnalyticsEvents.fireEvent;
61
+ var hasFiredSeenRef = useRef(false);
59
62
  var isFlexibleUi = useMemo(function () {
60
63
  return isFlexibleUiCard(children, ui);
61
64
  }, [children, ui]);
@@ -209,6 +212,19 @@ function Component(_ref) {
209
212
  });
210
213
  }
211
214
  }, [appearance, extensionKey, fireEvent, id, isFlexibleUi, state.status]);
215
+
216
+ // Fire smartLink seen event once on a successful render card is in the viewport
217
+ useEffect(function () {
218
+ if (fg('platform_sl_event_ui_seen')) {
219
+ if (!isFinalState(state.status)) return;
220
+ if (isIntersected && !hasFiredSeenRef.current) {
221
+ fireEvent('ui.smartLink.seen', {
222
+ display: appearance
223
+ });
224
+ hasFiredSeenRef.current = true;
225
+ }
226
+ }
227
+ }, [state.status, isIntersected, fireEvent, appearance]);
212
228
  var onIframeDwell = useCallback(function (dwellTime, dwellPercentVisible) {
213
229
  fireEvent('ui.smartLinkIframe.dwelled', {
214
230
  id: id,
@@ -331,6 +347,7 @@ function ComponentUpdated(_ref2) {
331
347
  url = _ref2.url,
332
348
  isSelected = _ref2.isSelected,
333
349
  isHovered = _ref2.isHovered,
350
+ isIntersected = _ref2.isIntersected,
334
351
  frameStyle = _ref2.frameStyle,
335
352
  platform = _ref2.platform,
336
353
  onClick = _ref2.onClick,
@@ -358,6 +375,7 @@ function ComponentUpdated(_ref2) {
358
375
  createAnalyticsEvent = _useAnalyticsEventsNe2.createAnalyticsEvent;
359
376
  var _useAnalyticsEvents2 = useAnalyticsEvents(),
360
377
  fireEvent = _useAnalyticsEvents2.fireEvent;
378
+ var hasFiredSeenRef = useRef(false);
361
379
  var isFlexibleUi = useMemo(function () {
362
380
  return isFlexibleUiCard(children, ui);
363
381
  }, [children, ui]);
@@ -511,6 +529,19 @@ function ComponentUpdated(_ref2) {
511
529
  });
512
530
  }
513
531
  }, [appearance, extensionKey, fireEvent, id, isFlexibleUi, state.status]);
532
+
533
+ // Fire smartLink seen event once on a successful render card is in the viewport
534
+ useEffect(function () {
535
+ if (fg('platform_sl_event_ui_seen')) {
536
+ if (!isFinalState(state.status)) return;
537
+ if (isIntersected && !hasFiredSeenRef.current) {
538
+ fireEvent('ui.smartLink.seen', {
539
+ display: appearance
540
+ });
541
+ hasFiredSeenRef.current = true;
542
+ }
543
+ }
544
+ }, [state.status, isIntersected, fireEvent, appearance]);
514
545
  var onIframeDwell = useCallback(function (dwellTime, dwellPercentVisible) {
515
546
  fireEvent('ui.smartLinkIframe.dwelled', {
516
547
  id: id,
@@ -655,4 +686,5 @@ export var CardWithUrlContent = function CardWithUrlContent(props) {
655
686
  id: props.id,
656
687
  display: display
657
688
  }, /*#__PURE__*/React.createElement(CardWithUrlContentComponent, props)));
658
- };
689
+ };
690
+ export var CardWithUrl = withCardIntersectionObserver(CardWithUrlContent);
@@ -15,7 +15,7 @@ import LinkWarningModal from './LinkWarningModal';
15
15
  import { useLinkWarningModal } from './LinkWarningModal/hooks/use-link-warning-modal';
16
16
  var PACKAGE_DATA = {
17
17
  packageName: "@atlaskit/smart-card",
18
- packageVersion: "44.2.0",
18
+ packageVersion: "44.3.1",
19
19
  componentName: 'linkUrl'
20
20
  };
21
21
  var Anchor = withLinkClickedEvent('a');
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Generates Typescript types for analytics events from analytics.spec.yaml
5
5
  *
6
- * @codegen <<SignedSource::1c5f802d8b5609627e5ffbebf73bad6e>>
6
+ * @codegen <<SignedSource::4d978bf4946aee0ad737d8305df7a6a0>>
7
7
  * @codegenCommand yarn workspace @atlassian/analytics-tooling run analytics:codegen smart-card
8
8
  */
9
9
  export type PackageMetaDataContextType = {
@@ -263,6 +263,9 @@ export type SmartLinkRenderFailedAttributesType = {
263
263
  id: string | null;
264
264
  };
265
265
  export type ButtonClickedDismissAttributesType = {};
266
+ export type SmartLinkSeenAttributesType = {
267
+ display: 'inline' | 'block' | 'embed' | 'flexible';
268
+ };
266
269
  export type SmartLinkClickedSmartlinkClickAnalyticsWorkflowsAttributesType = {
267
270
  thirdPartyARI: string;
268
271
  eventName: string;
@@ -439,6 +442,9 @@ export type AnalyticsEventAttributes = {
439
442
  /**
440
443
  * fires an event that represents when a Smart Link renders unsuccessfully. */
441
444
  'ui.smartLink.renderFailed': SmartLinkRenderFailedAttributesType;
445
+ /**
446
+ * fires an event that represents when a rendered Smart Link is visible in the viewport for the first time */
447
+ 'ui.smartLink.seen': SmartLinkSeenAttributesType;
442
448
  /**
443
449
  * fires an event that represents when a user clicks on a Smart Link to track analytics for the 3P Workflows team */
444
450
  'ui.smartLink.clicked.smartlinkClickAnalyticsWorkflows': SmartLinkClickedSmartlinkClickAnalyticsWorkflowsAttributesType;
@@ -0,0 +1,6 @@
1
+ type UseIntersectionObserverProps = {
2
+ onIntersecting?: () => void;
3
+ onIntersection?: (isIntersecting: boolean) => void;
4
+ };
5
+ declare const useIntersectionObserver: ({ onIntersecting: onIntersectingCallback, onIntersection: onIntersectionCallback, }: UseIntersectionObserverProps) => import("react").MutableRefObject<HTMLDivElement | null>;
6
+ export default useIntersectionObserver;
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import type { CardProps } from '../../Card';
3
+ type CardErrorBoundaryProps = CardProps & Required<Pick<CardProps, 'id' | 'url'>>;
4
+ declare const CardErrorBoundary: ({ children, ...props }: React.PropsWithChildren<CardErrorBoundaryProps>) => React.JSX.Element;
5
+ export default CardErrorBoundary;