@atlaskit/react-ufo 5.2.8 → 5.2.10

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 (61) hide show
  1. package/AGENTS.md +118 -73
  2. package/CHANGELOG.md +14 -0
  3. package/dist/cjs/experience-trace-id-context/index.js +3 -3
  4. package/dist/cjs/interaction-metrics/index.js +1 -0
  5. package/dist/cjs/interaction-metrics-init/index.js +2 -1
  6. package/dist/cjs/vc/index.js +4 -2
  7. package/dist/cjs/vc/vc-observer-new/index.js +10 -4
  8. package/dist/cjs/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +84 -29
  9. package/dist/cjs/vc/vc-observer-new/metric-calculator/utils/detect-layout-shift-cause.js +164 -0
  10. package/dist/cjs/vc/vc-observer-new/viewport-observer/index.js +173 -34
  11. package/dist/cjs/vc/vc-observer-new/viewport-observer/intersection-observer/index.js +1 -1
  12. package/dist/cjs/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +2 -1
  13. package/dist/es2019/experience-trace-id-context/index.js +3 -3
  14. package/dist/es2019/interaction-metrics/index.js +1 -0
  15. package/dist/es2019/interaction-metrics-init/index.js +2 -1
  16. package/dist/es2019/vc/index.js +4 -2
  17. package/dist/es2019/vc/vc-observer-new/index.js +10 -5
  18. package/dist/es2019/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +61 -7
  19. package/dist/es2019/vc/vc-observer-new/metric-calculator/utils/detect-layout-shift-cause.js +138 -0
  20. package/dist/es2019/vc/vc-observer-new/metric-calculator/vc-next/index.js +1 -1
  21. package/dist/es2019/vc/vc-observer-new/viewport-observer/index.js +145 -10
  22. package/dist/es2019/vc/vc-observer-new/viewport-observer/intersection-observer/index.js +1 -1
  23. package/dist/es2019/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +2 -1
  24. package/dist/esm/experience-trace-id-context/index.js +3 -3
  25. package/dist/esm/interaction-metrics/index.js +1 -0
  26. package/dist/esm/interaction-metrics-init/index.js +2 -1
  27. package/dist/esm/vc/index.js +4 -2
  28. package/dist/esm/vc/vc-observer-new/index.js +10 -4
  29. package/dist/esm/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +84 -29
  30. package/dist/esm/vc/vc-observer-new/metric-calculator/utils/detect-layout-shift-cause.js +158 -0
  31. package/dist/esm/vc/vc-observer-new/metric-calculator/vc-next/index.js +1 -1
  32. package/dist/esm/vc/vc-observer-new/viewport-observer/index.js +173 -34
  33. package/dist/esm/vc/vc-observer-new/viewport-observer/intersection-observer/index.js +1 -1
  34. package/dist/esm/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +2 -1
  35. package/dist/types/common/vc/types.d.ts +55 -0
  36. package/dist/types/config/index.d.ts +1 -0
  37. package/dist/types/create-payload/utils/get-visibility-state-from-performance.d.ts +1 -1
  38. package/dist/types/vc/types.d.ts +2 -0
  39. package/dist/types/vc/vc-observer-new/index.d.ts +2 -0
  40. package/dist/types/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.d.ts +1 -1
  41. package/dist/types/vc/vc-observer-new/metric-calculator/types.d.ts +1 -0
  42. package/dist/types/vc/vc-observer-new/metric-calculator/utils/detect-layout-shift-cause.d.ts +31 -0
  43. package/dist/types/vc/vc-observer-new/metric-calculator/vc-next/index.d.ts +2 -2
  44. package/dist/types/vc/vc-observer-new/types.d.ts +2 -0
  45. package/dist/types/vc/vc-observer-new/viewport-observer/index.d.ts +3 -1
  46. package/dist/types/vc/vc-observer-new/viewport-observer/mutation-observer/index.d.ts +1 -0
  47. package/dist/types/vc/vc-observer-new/viewport-observer/types.d.ts +5 -2
  48. package/dist/types-ts4.5/common/vc/types.d.ts +55 -0
  49. package/dist/types-ts4.5/config/index.d.ts +1 -0
  50. package/dist/types-ts4.5/create-payload/utils/get-visibility-state-from-performance.d.ts +1 -1
  51. package/dist/types-ts4.5/vc/types.d.ts +2 -0
  52. package/dist/types-ts4.5/vc/vc-observer-new/index.d.ts +2 -0
  53. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.d.ts +1 -1
  54. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/types.d.ts +1 -0
  55. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/utils/detect-layout-shift-cause.d.ts +31 -0
  56. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/vc-next/index.d.ts +2 -2
  57. package/dist/types-ts4.5/vc/vc-observer-new/types.d.ts +2 -0
  58. package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/index.d.ts +3 -1
  59. package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/mutation-observer/index.d.ts +1 -0
  60. package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/types.d.ts +5 -2
  61. package/package.json +1 -1
@@ -97,22 +97,151 @@ var createElementMutationsWatcher = function createElementMutationsWatcher(remov
97
97
  return 'mutation:element';
98
98
  };
99
99
  };
100
+ var createElementMutationsWatcherNew = function createElementMutationsWatcherNew(removedNodeRects, isWithinThirdPartySegment, isWithinSmartAnswersSegment, hasSameDeletedNode, timestamp, isTargetReactRoot, getSSRState, getSSRPlaceholderHandler) {
101
+ return function (_ref2) {
102
+ var target = _ref2.target,
103
+ rect = _ref2.rect;
104
+ if (getSSRState) {
105
+ var ssrState = getSSRState();
106
+ var SSRStateEnum = {
107
+ normal: 1,
108
+ waitingForFirstRender: 2,
109
+ ignoring: 3
110
+ };
111
+ if (ssrState.state === SSRStateEnum.waitingForFirstRender && timestamp > ssrState.renderStart && isTargetReactRoot) {
112
+ ssrState.state = SSRStateEnum.ignoring;
113
+ if (ssrState.renderStop === -1) {
114
+ // arbitrary 500ms DOM update window
115
+ ssrState.renderStop = timestamp + 500;
116
+ }
117
+ return {
118
+ type: 'ssr-hydration',
119
+ mutationData: {
120
+ timestamp: timestamp
121
+ }
122
+ };
123
+ }
124
+ if (ssrState.state === SSRStateEnum.ignoring && timestamp > ssrState.renderStart && isTargetReactRoot) {
125
+ if (timestamp <= ssrState.renderStop) {
126
+ return {
127
+ type: 'ssr-hydration',
128
+ mutationData: {
129
+ timestamp: timestamp
130
+ }
131
+ };
132
+ } else {
133
+ ssrState.state = SSRStateEnum.normal;
134
+ }
135
+ }
136
+ }
137
+ if (getSSRPlaceholderHandler) {
138
+ var ssrPlaceholderHandler = getSSRPlaceholderHandler();
139
+ if (ssrPlaceholderHandler) {
140
+ if ((ssrPlaceholderHandler.isPlaceholderV4(target) || ssrPlaceholderHandler.isPlaceholderIgnored(target)) && ssrPlaceholderHandler.checkIfExistedAndSizeMatchingV3(target)) {
141
+ return {
142
+ type: 'mutation:ssr-placeholder',
143
+ mutationData: {
144
+ timestamp: timestamp
145
+ }
146
+ };
147
+ }
148
+ if ((ssrPlaceholderHandler.isPlaceholderReplacementV4(target) || ssrPlaceholderHandler.isPlaceholderIgnored(target)) && ssrPlaceholderHandler.validateReactComponentMatchToPlaceholderV4(target)) {
149
+ return {
150
+ type: 'mutation:ssr-placeholder',
151
+ mutationData: {
152
+ timestamp: timestamp
153
+ }
154
+ };
155
+ }
156
+ }
157
+ }
158
+ if (hasSameDeletedNode && (0, _isInVcIgnoreIfNoLayoutShiftMarker.default)(target)) {
159
+ return {
160
+ type: 'mutation:remount',
161
+ mutationData: {
162
+ timestamp: timestamp
163
+ }
164
+ };
165
+ }
166
+ if ((0, _vcUtils.isContainedWithinMediaWrapper)(target)) {
167
+ return {
168
+ type: 'mutation:media',
169
+ mutationData: {
170
+ timestamp: timestamp
171
+ }
172
+ };
173
+ }
174
+ if (isWithinThirdPartySegment) {
175
+ return {
176
+ type: 'mutation:third-party-element',
177
+ mutationData: {
178
+ timestamp: timestamp
179
+ }
180
+ };
181
+ }
182
+ if (isWithinSmartAnswersSegment) {
183
+ return {
184
+ type: 'mutation:smart-answers-element',
185
+ mutationData: {
186
+ timestamp: timestamp
187
+ }
188
+ };
189
+ }
190
+ var isInIgnoreLsMarker = (0, _isInVcIgnoreIfNoLayoutShiftMarker.default)(target);
191
+ if (!isInIgnoreLsMarker) {
192
+ return {
193
+ type: 'mutation:element',
194
+ mutationData: {
195
+ timestamp: timestamp
196
+ }
197
+ };
198
+ }
199
+ var isRLLPlaceholder = _rllPlaceholders.RLLPlaceholderHandlers.getInstance().isRLLPlaceholderHydration(rect);
200
+ if (isRLLPlaceholder && isInIgnoreLsMarker) {
201
+ return {
202
+ type: 'mutation:rll-placeholder',
203
+ mutationData: {
204
+ timestamp: timestamp
205
+ }
206
+ };
207
+ }
208
+ var wasDeleted = removedNodeRects.some(function (nr) {
209
+ return (0, _isSameRectDimensions.isSameRectDimensions)(nr, rect);
210
+ });
211
+ if (wasDeleted && isInIgnoreLsMarker) {
212
+ return {
213
+ type: 'mutation:element-replacement',
214
+ mutationData: {
215
+ timestamp: timestamp
216
+ }
217
+ };
218
+ }
219
+ return {
220
+ type: 'mutation:element',
221
+ mutationData: {
222
+ timestamp: timestamp
223
+ }
224
+ };
225
+ };
226
+ };
100
227
  var ViewportObserver = exports.default = /*#__PURE__*/function () {
101
228
  // SSR context functions
102
229
 
103
- function ViewportObserver(_ref2) {
230
+ function ViewportObserver(_ref3) {
104
231
  var _this = this;
105
- var onChange = _ref2.onChange,
106
- getSSRState = _ref2.getSSRState,
107
- getSSRPlaceholderHandler = _ref2.getSSRPlaceholderHandler,
108
- searchPageConfig = _ref2.searchPageConfig;
232
+ var onChange = _ref3.onChange,
233
+ getSSRState = _ref3.getSSRState,
234
+ getSSRPlaceholderHandler = _ref3.getSSRPlaceholderHandler,
235
+ _ref3$trackLayoutShif = _ref3.trackLayoutShiftOffenders,
236
+ trackLayoutShiftOffenders = _ref3$trackLayoutShif === void 0 ? false : _ref3$trackLayoutShif,
237
+ searchPageConfig = _ref3.searchPageConfig;
109
238
  (0, _classCallCheck2.default)(this, ViewportObserver);
110
- (0, _defineProperty2.default)(this, "handleIntersectionEntry", function (_ref3) {
111
- var target = _ref3.target,
112
- rect = _ref3.rect,
113
- time = _ref3.time,
114
- type = _ref3.type,
115
- mutationData = _ref3.mutationData;
239
+ (0, _defineProperty2.default)(this, "handleIntersectionEntry", function (_ref4) {
240
+ var target = _ref4.target,
241
+ rect = _ref4.rect,
242
+ time = _ref4.time,
243
+ type = _ref4.type,
244
+ mutationData = _ref4.mutationData;
116
245
  if (!target) {
117
246
  return;
118
247
  }
@@ -130,12 +259,12 @@ var ViewportObserver = exports.default = /*#__PURE__*/function () {
130
259
  });
131
260
  });
132
261
  (0, _defineProperty2.default)(this, "handleChildListMutation", /*#__PURE__*/function () {
133
- var _ref5 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(_ref4) {
262
+ var _ref6 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(_ref5) {
134
263
  var target, addedNodes, removedNodes, timestamp, removedNodeRects, targetNode, _iterator, _step, _loop;
135
264
  return _regenerator.default.wrap(function _callee$(_context2) {
136
265
  while (1) switch (_context2.prev = _context2.next) {
137
266
  case 0:
138
- target = _ref4.target, addedNodes = _ref4.addedNodes, removedNodes = _ref4.removedNodes, timestamp = _ref4.timestamp;
267
+ target = _ref5.target, addedNodes = _ref5.addedNodes, removedNodes = _ref5.removedNodes, timestamp = _ref5.timestamp;
139
268
  removedNodeRects = removedNodes.map(function (ref) {
140
269
  var n = ref.deref();
141
270
  if (!n) {
@@ -170,7 +299,7 @@ var ViewportObserver = exports.default = /*#__PURE__*/function () {
170
299
  _checkWithinComponent = (0, _checkWithinComponent3.default)(addedNode, 'UFOThirdPartySegment', _this.mapIs3pResult), isWithinThirdPartySegment = _checkWithinComponent.isWithin;
171
300
  isWithinSmartAnswersSegment = Boolean(_this.shouldCheckSmartAnswersMutations() && (0, _isContainedWithinSmartAnswers.isContainedWithinSmartAnswers)(addedNode));
172
301
  isTargetReactRoot = targetNode === ((_this$getSSRState = _this.getSSRState) === null || _this$getSSRState === void 0 || (_this$getSSRState = _this$getSSRState.call(_this)) === null || _this$getSSRState === void 0 ? void 0 : _this$getSSRState.reactRootElement);
173
- (_this$intersectionObs = _this.intersectionObserver) === null || _this$intersectionObs === void 0 || _this$intersectionObs.watchAndTag(addedNode, createElementMutationsWatcher(removedNodeRects, isWithinThirdPartySegment, isWithinSmartAnswersSegment, !!hasSameDeletedNode, timestamp, isTargetReactRoot, _this.getSSRState, _this.getSSRPlaceholderHandler));
302
+ (_this$intersectionObs = _this.intersectionObserver) === null || _this$intersectionObs === void 0 || _this$intersectionObs.watchAndTag(addedNode, (_this.trackLayoutShiftOffenders ? createElementMutationsWatcherNew : createElementMutationsWatcher)(removedNodeRects, isWithinThirdPartySegment, isWithinSmartAnswersSegment, !!hasSameDeletedNode, timestamp, isTargetReactRoot, _this.getSSRState, _this.getSSRPlaceholderHandler));
174
303
  case 9:
175
304
  case "end":
176
305
  return _context.stop();
@@ -211,25 +340,27 @@ var ViewportObserver = exports.default = /*#__PURE__*/function () {
211
340
  }, _callee, null, [[4, 15, 18, 21]]);
212
341
  }));
213
342
  return function (_x) {
214
- return _ref5.apply(this, arguments);
343
+ return _ref6.apply(this, arguments);
215
344
  };
216
345
  }());
217
- (0, _defineProperty2.default)(this, "handleAttributeMutation", function (_ref6) {
346
+ (0, _defineProperty2.default)(this, "handleAttributeMutation", function (_ref7) {
218
347
  var _this$intersectionObs2;
219
- var target = _ref6.target,
220
- attributeName = _ref6.attributeName,
221
- oldValue = _ref6.oldValue,
222
- newValue = _ref6.newValue;
223
- (_this$intersectionObs2 = _this.intersectionObserver) === null || _this$intersectionObs2 === void 0 || _this$intersectionObs2.watchAndTag(target, function (_ref7) {
224
- var target = _ref7.target,
225
- rect = _ref7.rect;
348
+ var target = _ref7.target,
349
+ attributeName = _ref7.attributeName,
350
+ oldValue = _ref7.oldValue,
351
+ newValue = _ref7.newValue,
352
+ timestamp = _ref7.timestamp;
353
+ (_this$intersectionObs2 = _this.intersectionObserver) === null || _this$intersectionObs2 === void 0 || _this$intersectionObs2.watchAndTag(target, function (_ref8) {
354
+ var target = _ref8.target,
355
+ rect = _ref8.rect;
226
356
  if ((0, _vcUtils.isContainedWithinMediaWrapper)(target)) {
227
357
  return {
228
358
  type: 'mutation:media',
229
359
  mutationData: {
230
360
  attributeName: attributeName,
231
361
  oldValue: oldValue,
232
- newValue: newValue
362
+ newValue: newValue,
363
+ timestamp: timestamp
233
364
  }
234
365
  };
235
366
  }
@@ -242,7 +373,8 @@ var ViewportObserver = exports.default = /*#__PURE__*/function () {
242
373
  mutationData: {
243
374
  attributeName: attributeName,
244
375
  oldValue: oldValue,
245
- newValue: newValue
376
+ newValue: newValue,
377
+ timestamp: timestamp
246
378
  }
247
379
  };
248
380
  }
@@ -268,7 +400,8 @@ var ViewportObserver = exports.default = /*#__PURE__*/function () {
268
400
  mutationData: {
269
401
  attributeName: attributeName,
270
402
  oldValue: oldValue,
271
- newValue: newValue
403
+ newValue: newValue,
404
+ timestamp: timestamp
272
405
  }
273
406
  };
274
407
  }
@@ -282,7 +415,8 @@ var ViewportObserver = exports.default = /*#__PURE__*/function () {
282
415
  mutationData: {
283
416
  attributeName: attributeName,
284
417
  oldValue: oldValue,
285
- newValue: newValue
418
+ newValue: newValue,
419
+ timestamp: timestamp
286
420
  }
287
421
  };
288
422
  }
@@ -297,7 +431,8 @@ var ViewportObserver = exports.default = /*#__PURE__*/function () {
297
431
  mutationData: {
298
432
  attributeName: attributeName,
299
433
  oldValue: oldValue,
300
- newValue: newValue
434
+ newValue: newValue,
435
+ timestamp: timestamp
301
436
  }
302
437
  };
303
438
  }
@@ -308,7 +443,8 @@ var ViewportObserver = exports.default = /*#__PURE__*/function () {
308
443
  mutationData: {
309
444
  attributeName: attributeName,
310
445
  oldValue: oldValue,
311
- newValue: newValue
446
+ newValue: newValue,
447
+ timestamp: timestamp
312
448
  }
313
449
  };
314
450
  }
@@ -319,7 +455,8 @@ var ViewportObserver = exports.default = /*#__PURE__*/function () {
319
455
  mutationData: {
320
456
  attributeName: attributeName,
321
457
  oldValue: oldValue,
322
- newValue: newValue
458
+ newValue: newValue,
459
+ timestamp: timestamp
323
460
  }
324
461
  };
325
462
  }
@@ -328,14 +465,15 @@ var ViewportObserver = exports.default = /*#__PURE__*/function () {
328
465
  mutationData: {
329
466
  attributeName: attributeName,
330
467
  oldValue: oldValue,
331
- newValue: newValue
468
+ newValue: newValue,
469
+ timestamp: timestamp
332
470
  }
333
471
  };
334
472
  });
335
473
  });
336
- (0, _defineProperty2.default)(this, "handleLayoutShift", function (_ref8) {
337
- var time = _ref8.time,
338
- changedRects = _ref8.changedRects;
474
+ (0, _defineProperty2.default)(this, "handleLayoutShift", function (_ref9) {
475
+ var time = _ref9.time,
476
+ changedRects = _ref9.changedRects;
339
477
  var _iterator2 = _createForOfIteratorHelper(changedRects),
340
478
  _step2;
341
479
  try {
@@ -371,6 +509,7 @@ var ViewportObserver = exports.default = /*#__PURE__*/function () {
371
509
  this.intersectionObserver = null;
372
510
  this.mutationObserver = null;
373
511
  this.performanceObserver = null;
512
+ this.trackLayoutShiftOffenders = trackLayoutShiftOffenders;
374
513
 
375
514
  // Initialize SSR context functions
376
515
  this.getSSRState = getSSRState;
@@ -36,7 +36,7 @@ function createIntersectionObserver(_ref) {
36
36
  }
37
37
 
38
38
  // override as display-contents mutation
39
- if (tagOrCallbackResult && typeof tagOrCallbackResult !== 'string' && tagOrCallbackResult.type === 'mutation:attribute') {
39
+ if (tagOrCallbackResult && typeof tagOrCallbackResult !== 'string' && tagOrCallbackResult.type === 'mutation:attribute' && 'oldValue' in tagOrCallbackResult.mutationData) {
40
40
  var _tagOrCallbackResult$ = tagOrCallbackResult.mutationData,
41
41
  attributeName = _tagOrCallbackResult$.attributeName,
42
42
  oldValue = _tagOrCallbackResult$.oldValue,
@@ -58,7 +58,8 @@ function createMutationObserver(_ref) {
58
58
  target: mut.target,
59
59
  attributeName: (_mut$attributeName = mut.attributeName) !== null && _mut$attributeName !== void 0 ? _mut$attributeName : 'unknown',
60
60
  oldValue: oldValue,
61
- newValue: newValue
61
+ newValue: newValue,
62
+ timestamp: mut.timestamp || performance.now()
62
63
  });
63
64
  }
64
65
  return 0; // continue
@@ -5,9 +5,9 @@ import { makeTraceHttpRequestHeaders } from './utils/make-trace-http-request-hea
5
5
  const state = {
6
6
  context: null
7
7
  };
8
- const traceIdKey = createContextKey("traceId");
9
- const spanIdKey = createContextKey("spanId");
10
- const experienceTypeKey = createContextKey("type");
8
+ const traceIdKey = createContextKey('traceId');
9
+ const spanIdKey = createContextKey('spanId');
10
+ const experienceTypeKey = createContextKey('type');
11
11
 
12
12
  // DO NOT CALL THIS FUNCTION DIRECTLY!!!!
13
13
  // It is only to be called by React UFO libraries for the automatic handling of trace context for experiences.
@@ -1072,6 +1072,7 @@ export function addNewInteraction(interactionId, ufoName, type, startTime, rate,
1072
1072
  devToolsEnabled: config.vc.devToolsEnabled,
1073
1073
  selectorConfig: config.vc.selectorConfig,
1074
1074
  ssrEnablePageLayoutPlaceholder: config.vc.ssrEnablePageLayoutPlaceholder,
1075
+ trackLayoutShiftOffenders: config.vc.trackLayoutShiftOffenders,
1075
1076
  searchPageConfig
1076
1077
  };
1077
1078
  vcObserver = newVCObserver(vcOptions);
@@ -158,7 +158,8 @@ export function init(analyticsWebClientAsync, config) {
158
158
  oldDomUpdates: config.vc.oldDomUpdates,
159
159
  devToolsEnabled: config.vc.devToolsEnabled,
160
160
  selectorConfig: config.vc.selectorConfig,
161
- ssrEnablePageLayoutPlaceholder: config.vc.ssrEnablePageLayoutPlaceholder
161
+ ssrEnablePageLayoutPlaceholder: config.vc.ssrEnablePageLayoutPlaceholder,
162
+ trackLayoutShiftOffenders: config.vc.trackLayoutShiftOffenders
162
163
  };
163
164
  postInteractionLog.initializeVCObserver(vcOptions);
164
165
  if (config !== null && config !== void 0 && (_config$experimentalI = config.experimentalInteractionMetrics) !== null && _config$experimentalI !== void 0 && _config$experimentalI.enabled) {
@@ -16,7 +16,7 @@ export class VCObserverWrapper {
16
16
  enablePageLayoutPlaceholder: (_opts$ssrEnablePageLa = opts.ssrEnablePageLayoutPlaceholder) !== null && _opts$ssrEnablePageLa !== void 0 ? _opts$ssrEnablePageLa : false
17
17
  });
18
18
  if (isVCRevisionEnabled('fy25.03') || isVCRevisionEnabled('fy26.04')) {
19
- var _opts$ssrEnablePageLa2;
19
+ var _opts$ssrEnablePageLa2, _opts$trackLayoutShif;
20
20
  this.newVCObserver = new VCObserverNew({
21
21
  selectorConfig: opts.selectorConfig,
22
22
  isPostInteraction: opts.isPostInteraction,
@@ -24,6 +24,7 @@ export class VCObserverWrapper {
24
24
  enablePageLayoutPlaceholder: (_opts$ssrEnablePageLa2 = opts.ssrEnablePageLayoutPlaceholder) !== null && _opts$ssrEnablePageLa2 !== void 0 ? _opts$ssrEnablePageLa2 : false
25
25
  },
26
26
  ssrPlaceholderHandler: this.ssrPlaceholderHandler,
27
+ trackLayoutShiftOffenders: (_opts$trackLayoutShif = opts.trackLayoutShiftOffenders) !== null && _opts$trackLayoutShif !== void 0 ? _opts$trackLayoutShif : false,
27
28
  searchPageConfig: opts.searchPageConfig
28
29
  });
29
30
  }
@@ -115,7 +116,8 @@ export class VCObserverWrapper {
115
116
  interactionAbortReason: param.interactionAbortReason,
116
117
  includeRawData,
117
118
  includeSSRInV3: param.includeSSRInV3,
118
- rawDataStopTime: param.rawDataStopTime
119
+ rawDataStopTime: param.rawDataStopTime,
120
+ reportLayoutShiftOffenders: param.reportLayoutShiftOffenders
119
121
  })) : [];
120
122
  if (!v3v4Result || v3v4Result.length === 0) {
121
123
  return v1v2Result !== null && v1v2Result !== void 0 ? v1v2Result : {};
@@ -31,7 +31,7 @@ export function getHasAbortingEventDuringSSR() {
31
31
  }
32
32
  export default class VCObserverNew {
33
33
  constructor(config) {
34
- var _config$isPostInterac, _config$selectorConfi;
34
+ var _config$isPostInterac, _config$selectorConfi, _config$trackLayoutSh;
35
35
  _defineProperty(this, "viewportObserver", null);
36
36
  _defineProperty(this, "windowEventObserver", null);
37
37
  // SSR related properties
@@ -55,6 +55,7 @@ export default class VCObserverNew {
55
55
  enablePageLayoutPlaceholder: (_config$SSRConfig$ena = (_config$SSRConfig = config.SSRConfig) === null || _config$SSRConfig === void 0 ? void 0 : _config$SSRConfig.enablePageLayoutPlaceholder) !== null && _config$SSRConfig$ena !== void 0 ? _config$SSRConfig$ena : false
56
56
  });
57
57
  }
58
+ this.trackLayoutShiftOffenders = (_config$trackLayoutSh = config.trackLayoutShiftOffenders) !== null && _config$trackLayoutSh !== void 0 ? _config$trackLayoutSh : false;
58
59
  this.viewportObserver = new ViewportObserver({
59
60
  onChange: onChangeArg => {
60
61
  const {
@@ -79,7 +80,8 @@ export default class VCObserverNew {
79
80
  visible,
80
81
  attributeName: mutationData === null || mutationData === void 0 ? void 0 : mutationData.attributeName,
81
82
  oldValue: mutationData === null || mutationData === void 0 ? void 0 : mutationData.oldValue,
82
- newValue: mutationData === null || mutationData === void 0 ? void 0 : mutationData.newValue
83
+ newValue: mutationData === null || mutationData === void 0 ? void 0 : mutationData.newValue,
84
+ originalMutationTimestamp: mutationData === null || mutationData === void 0 ? void 0 : mutationData.timestamp
83
85
  };
84
86
  if (element) {
85
87
  const labelStacks = getLabelStacks(element);
@@ -97,7 +99,8 @@ export default class VCObserverNew {
97
99
  // Pass SSR context to ViewportObserver
98
100
  getSSRState: () => this.getSSRState(),
99
101
  getSSRPlaceholderHandler: () => this.getSSRPlaceholderHandler(),
100
- searchPageConfig: config.searchPageConfig
102
+ searchPageConfig: config.searchPageConfig,
103
+ trackLayoutShiftOffenders: this.trackLayoutShiftOffenders
101
104
  });
102
105
  this.windowEventObserver = new WindowEventObserver({
103
106
  onEvent: ({
@@ -271,7 +274,8 @@ export default class VCObserverNew {
271
274
  excludeSmartAnswersInSearch,
272
275
  includeSSRRatio,
273
276
  isPageVisible,
274
- interactionAbortReason
277
+ interactionAbortReason,
278
+ reportLayoutShiftOffenders: this.trackLayoutShiftOffenders
275
279
  }) : null;
276
280
  if (fy25_03) {
277
281
  results.push(fy25_03);
@@ -294,7 +298,8 @@ export default class VCObserverNew {
294
298
  excludeSmartAnswersInSearch,
295
299
  includeSSRRatio,
296
300
  isPageVisible,
297
- interactionAbortReason
301
+ interactionAbortReason,
302
+ reportLayoutShiftOffenders: this.trackLayoutShiftOffenders
298
303
  };
299
304
  const [fy26_04, vcNext] = await Promise.all([isVCRevisionEnabled('fy26.04') ? calculator_fy26_04.calculate(calculatorParams) : null, isVCRevisionEnabled('next') ? calculator_next.calculate(calculatorParams) : null]);
300
305
  if (fy26_04) {
@@ -1,5 +1,6 @@
1
1
  import { fg } from '@atlaskit/platform-feature-flags';
2
2
  import { calculateTTVCPercentilesWithDebugInfo } from './percentile-calc';
3
+ import { detectLayoutShiftCause } from './utils/detect-layout-shift-cause';
3
4
  import getViewportHeight from './utils/get-viewport-height';
4
5
  import getViewportWidth from './utils/get-viewport-width';
5
6
 
@@ -60,7 +61,7 @@ export default class AbstractVCCalculatorBase {
60
61
  }
61
62
  return labelStacks;
62
63
  }
63
- async calculateWithDebugInfo(filteredEntries, startTime, stopTime, isPostInteraction, isVCClean, interactionType, isPageVisible, interactionId, dirtyReason, allEntries, include3p, excludeSmartAnswersInSearch, interactionAbortReason, includeSSRRatio) {
64
+ async calculateWithDebugInfo(filteredEntries, startTime, stopTime, isPostInteraction, isVCClean, interactionType, isPageVisible, interactionId, dirtyReason, allEntries, include3p, excludeSmartAnswersInSearch, interactionAbortReason, includeSSRRatio, reportLayoutShiftOffenders) {
64
65
  var _window, _window2, _window3, _window4, _window6;
65
66
  const percentiles = [25, 50, 75, 80, 85, 90, 95, 98, 99, 100];
66
67
  const viewportEntries = this.filterViewportEntries(filteredEntries);
@@ -80,6 +81,8 @@ export default class AbstractVCCalculatorBase {
80
81
  let percentileIndex = 0;
81
82
  const entryDataBuffer = new Set();
82
83
  let ssrRatio = -1;
84
+ let layoutShiftInsights = null;
85
+ let previousViewportPercentage = 0;
83
86
  if (vcLogs) {
84
87
  for (const entry of vcLogs) {
85
88
  const {
@@ -106,6 +109,19 @@ export default class AbstractVCCalculatorBase {
106
109
  t: Math.round(time),
107
110
  e: elementNames
108
111
  };
112
+ if (reportLayoutShiftOffenders && percentiles[percentileIndex] === 90 && entries.some(e => e.type === 'layout-shift')) {
113
+ const layoutShiftEntries = entries.filter(e => e.type === 'layout-shift');
114
+ layoutShiftInsights = {
115
+ layoutShiftOffendersResult: detectLayoutShiftCause({
116
+ viewportEntries: viewportEntries,
117
+ layoutShiftEntries,
118
+ time,
119
+ startTime
120
+ }),
121
+ layoutShiftEntriesCount: layoutShiftEntries.length,
122
+ layoutShiftImpact: viewportPercentage - previousViewportPercentage
123
+ };
124
+ }
109
125
  percentileIndex++;
110
126
  }
111
127
 
@@ -115,6 +131,7 @@ export default class AbstractVCCalculatorBase {
115
131
  // Only add to buffer if we haven't reached all percentiles
116
132
  entries.forEach(e => entryDataBuffer.add(e));
117
133
  }
134
+ previousViewportPercentage = viewportPercentage;
118
135
  }
119
136
  }
120
137
 
@@ -262,7 +279,8 @@ export default class AbstractVCCalculatorBase {
262
279
  return {
263
280
  vcDetails,
264
281
  ssrRatio,
265
- speedIndex
282
+ speedIndex,
283
+ VC90layoutShiftInsights: layoutShiftInsights
266
284
  };
267
285
  }
268
286
  async calculate({
@@ -276,9 +294,10 @@ export default class AbstractVCCalculatorBase {
276
294
  includeSSRRatio,
277
295
  interactionType,
278
296
  isPageVisible,
279
- interactionAbortReason
297
+ interactionAbortReason,
298
+ reportLayoutShiftOffenders
280
299
  }) {
281
- var _vcDetails$90$t, _vcDetails$;
300
+ var _vcDetails$90$t, _vcDetails$, _layoutShiftInsightsP;
282
301
  const filteredEntries = orderedEntries.filter(entry => {
283
302
  return this.isEntryIncluded(entry, include3p, excludeSmartAnswersInSearch);
284
303
  });
@@ -299,13 +318,48 @@ export default class AbstractVCCalculatorBase {
299
318
  const {
300
319
  vcDetails,
301
320
  ssrRatio,
302
- speedIndex
303
- } = await this.calculateWithDebugInfo(filteredEntries, startTime, stopTime, isPostInteraction, isVCClean, interactionType, isPageVisible, interactionId, dirtyReason, orderedEntries, include3p, excludeSmartAnswersInSearch, interactionAbortReason, includeSSRRatio);
321
+ speedIndex,
322
+ VC90layoutShiftInsights
323
+ } = await this.calculateWithDebugInfo(filteredEntries, startTime, stopTime, isPostInteraction, isVCClean, interactionType, isPageVisible, interactionId, dirtyReason, orderedEntries, include3p, excludeSmartAnswersInSearch, interactionAbortReason, includeSSRRatio, reportLayoutShiftOffenders);
324
+ let layoutShiftInsightsPayload;
325
+ if (VC90layoutShiftInsights !== null && reportLayoutShiftOffenders) {
326
+ var _layoutShiftOffenders, _layoutShiftOffenders2, _layoutShiftOffenders3;
327
+ const {
328
+ layoutShiftOffendersResult,
329
+ layoutShiftEntriesCount,
330
+ layoutShiftImpact
331
+ } = VC90layoutShiftInsights;
332
+ layoutShiftInsightsPayload = {
333
+ impact: layoutShiftImpact,
334
+ sources: layoutShiftEntriesCount !== null && layoutShiftEntriesCount !== void 0 ? layoutShiftEntriesCount : 0,
335
+ same: {
336
+ dir: (_layoutShiftOffenders = layoutShiftOffendersResult === null || layoutShiftOffendersResult === void 0 ? void 0 : layoutShiftOffendersResult.layoutShiftVariables.allMovedSameWay) !== null && _layoutShiftOffenders !== void 0 ? _layoutShiftOffenders : false,
337
+ dist: (_layoutShiftOffenders2 = layoutShiftOffendersResult === null || layoutShiftOffendersResult === void 0 ? void 0 : layoutShiftOffendersResult.layoutShiftVariables.allMovedSameAmount) !== null && _layoutShiftOffenders2 !== void 0 ? _layoutShiftOffenders2 : false
338
+ },
339
+ total_mut: (_layoutShiftOffenders3 = layoutShiftOffendersResult === null || layoutShiftOffendersResult === void 0 ? void 0 : layoutShiftOffendersResult.layoutShiftOffenders.length) !== null && _layoutShiftOffenders3 !== void 0 ? _layoutShiftOffenders3 : 0,
340
+ mut: layoutShiftOffendersResult === null || layoutShiftOffendersResult === void 0 ? void 0 : layoutShiftOffendersResult.layoutShiftOffenders.sort((a, b) => Math.abs(a.distanceToLS) - Math.abs(b.distanceToLS)).slice(0, 5).map(offender => ({
341
+ e: offender.offender,
342
+ size: -1,
343
+ // @todo: calculate size
344
+ attr: {
345
+ t_before: offender.happenedBefore,
346
+ t_distance: offender.distanceToLS,
347
+ p_above: offender.isAbove,
348
+ p_left: offender.isLeft,
349
+ p_right: offender.isRight,
350
+ p_h_overlap: offender.hasHorizontalOverlap,
351
+ p_v_overlap: offender.hasVerticalOverlap,
352
+ p_same_offset: offender.matchesLayoutShiftDelta ? 'all' : 'none'
353
+ }
354
+ }))
355
+ };
356
+ }
304
357
  const result = {
305
358
  revision: this.revisionNo,
306
359
  clean: true,
307
360
  'metric:vc90': (_vcDetails$90$t = vcDetails === null || vcDetails === void 0 ? void 0 : (_vcDetails$ = vcDetails['90']) === null || _vcDetails$ === void 0 ? void 0 : _vcDetails$.t) !== null && _vcDetails$90$t !== void 0 ? _vcDetails$90$t : null,
308
- vcDetails: vcDetails !== null && vcDetails !== void 0 ? vcDetails : undefined
361
+ vcDetails: vcDetails !== null && vcDetails !== void 0 ? vcDetails : undefined,
362
+ 'vc90:ls': (_layoutShiftInsightsP = layoutShiftInsightsPayload) !== null && _layoutShiftInsightsP !== void 0 ? _layoutShiftInsightsP : undefined
309
363
  };
310
364
  result.ratios = this.calculateRatios(filteredEntries);
311
365
  if (ssrRatio !== -1) {