@atlaskit/react-ufo 3.14.6 → 3.14.7

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 (41) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cjs/vc/index.js +39 -6
  3. package/dist/cjs/vc/vc-observer/index.js +10 -2
  4. package/dist/cjs/vc/vc-observer/observers/index.js +12 -7
  5. package/dist/cjs/vc/vc-observer/observers/ssr-placeholders/index.js +76 -40
  6. package/dist/cjs/vc/vc-observer-new/index.js +84 -0
  7. package/dist/cjs/vc/vc-observer-new/viewport-observer/index.js +214 -71
  8. package/dist/cjs/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +97 -59
  9. package/dist/es2019/vc/index.js +37 -5
  10. package/dist/es2019/vc/vc-observer/index.js +8 -2
  11. package/dist/es2019/vc/vc-observer/observers/index.js +11 -5
  12. package/dist/es2019/vc/vc-observer/observers/ssr-placeholders/index.js +57 -26
  13. package/dist/es2019/vc/vc-observer-new/index.js +67 -1
  14. package/dist/es2019/vc/vc-observer-new/viewport-observer/index.js +87 -22
  15. package/dist/es2019/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +50 -34
  16. package/dist/esm/vc/index.js +39 -6
  17. package/dist/esm/vc/vc-observer/index.js +10 -2
  18. package/dist/esm/vc/vc-observer/observers/index.js +12 -7
  19. package/dist/esm/vc/vc-observer/observers/ssr-placeholders/index.js +76 -40
  20. package/dist/esm/vc/vc-observer-new/index.js +84 -0
  21. package/dist/esm/vc/vc-observer-new/viewport-observer/index.js +214 -71
  22. package/dist/esm/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +97 -59
  23. package/dist/types/vc/index.d.ts +2 -0
  24. package/dist/types/vc/types.d.ts +2 -0
  25. package/dist/types/vc/vc-observer/index.d.ts +1 -0
  26. package/dist/types/vc/vc-observer/observers/index.d.ts +2 -0
  27. package/dist/types/vc/vc-observer/observers/ssr-placeholders/index.d.ts +6 -0
  28. package/dist/types/vc/vc-observer-new/index.d.ts +30 -0
  29. package/dist/types/vc/vc-observer-new/types.d.ts +1 -1
  30. package/dist/types/vc/vc-observer-new/viewport-observer/index.d.ts +5 -1
  31. package/dist/types/vc/vc-observer-new/viewport-observer/mutation-observer/index.d.ts +2 -0
  32. package/dist/types-ts4.5/vc/index.d.ts +2 -0
  33. package/dist/types-ts4.5/vc/types.d.ts +2 -0
  34. package/dist/types-ts4.5/vc/vc-observer/index.d.ts +1 -0
  35. package/dist/types-ts4.5/vc/vc-observer/observers/index.d.ts +2 -0
  36. package/dist/types-ts4.5/vc/vc-observer/observers/ssr-placeholders/index.d.ts +6 -0
  37. package/dist/types-ts4.5/vc/vc-observer-new/index.d.ts +30 -0
  38. package/dist/types-ts4.5/vc/vc-observer-new/types.d.ts +1 -1
  39. package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/index.d.ts +5 -1
  40. package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/mutation-observer/index.d.ts +2 -0
  41. package/package.json +5 -3
@@ -21,7 +21,6 @@ function isElementVisible(target) {
21
21
  }
22
22
  export class Observers {
23
23
  constructor(opts) {
24
- var _opts$SSRConfig, _opts$SSRConfig2;
25
24
  _defineProperty(this, "observedMutations", new WeakMap());
26
25
  _defineProperty(this, "elementsInView", new Set());
27
26
  _defineProperty(this, "callbacks", new Set());
@@ -61,10 +60,17 @@ export class Observers {
61
60
  };
62
61
  this.intersectionObserver = this.getIntersectionObserver();
63
62
  this.mutationObserver = this.getMutationObserver();
64
- this.ssrPlaceholderHandler = new SSRPlaceholderHandlers({
65
- enablePageLayoutPlaceholder: (_opts$SSRConfig = opts.SSRConfig) === null || _opts$SSRConfig === void 0 ? void 0 : _opts$SSRConfig.enablePageLayoutPlaceholder,
66
- disableSizeAndPositionCheck: (_opts$SSRConfig2 = opts.SSRConfig) === null || _opts$SSRConfig2 === void 0 ? void 0 : _opts$SSRConfig2.disableSizeAndPositionCheck
67
- });
63
+
64
+ // Use shared SSR placeholder handler if provided, otherwise create new one
65
+ if (opts.ssrPlaceholderHandler) {
66
+ this.ssrPlaceholderHandler = opts.ssrPlaceholderHandler;
67
+ } else {
68
+ var _opts$SSRConfig, _opts$SSRConfig2;
69
+ this.ssrPlaceholderHandler = new SSRPlaceholderHandlers({
70
+ enablePageLayoutPlaceholder: (_opts$SSRConfig = opts.SSRConfig) === null || _opts$SSRConfig === void 0 ? void 0 : _opts$SSRConfig.enablePageLayoutPlaceholder,
71
+ disableSizeAndPositionCheck: (_opts$SSRConfig2 = opts.SSRConfig) === null || _opts$SSRConfig2 === void 0 ? void 0 : _opts$SSRConfig2.disableSizeAndPositionCheck
72
+ });
73
+ }
68
74
  }
69
75
  isBrowserSupported() {
70
76
  return typeof window.IntersectionObserver === 'function' && typeof window.MutationObserver === 'function';
@@ -96,32 +96,8 @@ export class SSRPlaceholderHandlers {
96
96
  this.disableSizeAndPositionCheck = disableSizeAndPositionCheck;
97
97
  if (window.document) {
98
98
  try {
99
- const selector = this.enablePageLayoutPlaceholder ? '[data-ssr-placeholder],[data-testid="page-layout.root"]' : '[data-ssr-placeholder]';
100
- const existingElements = document.querySelectorAll(selector);
101
- existingElements.forEach(el => {
102
- const placeholderId = el instanceof HTMLElement && this.getPlaceholderId(el);
103
- if (placeholderId) {
104
- var _window$__SSR_PLACEHO, _this$intersectionObs2;
105
- let width = -1;
106
- let height = -1;
107
- let x = -1;
108
- let y = -1;
109
- const boundingClientRect = (_window$__SSR_PLACEHO = window.__SSR_PLACEHOLDERS_DIMENSIONS__) === null || _window$__SSR_PLACEHO === void 0 ? void 0 : _window$__SSR_PLACEHO[placeholderId];
110
- if (boundingClientRect) {
111
- width = boundingClientRect.width;
112
- height = boundingClientRect.height;
113
- x = boundingClientRect.x;
114
- y = boundingClientRect.y;
115
- }
116
- this.staticPlaceholders.set(placeholderId, {
117
- width,
118
- height,
119
- x,
120
- y
121
- });
122
- (_this$intersectionObs2 = this.intersectionObserver) === null || _this$intersectionObs2 === void 0 ? void 0 : _this$intersectionObs2.observe(el);
123
- }
124
- });
99
+ // Collect initial placeholders using SSR dimensions
100
+ this.collectPlaceholdersInternal();
125
101
  } catch (e) {} finally {
126
102
  delete window.__SSR_PLACEHOLDERS_DIMENSIONS__;
127
103
  }
@@ -133,6 +109,61 @@ export class SSRPlaceholderHandlers {
133
109
  this.getSizeCallbacks = new Map();
134
110
  this.reactValidateCallbacks = new Map();
135
111
  }
112
+ collectPlaceholdersInternal() {
113
+ const selector = this.enablePageLayoutPlaceholder ? '[data-ssr-placeholder],[data-testid="page-layout.root"]' : '[data-ssr-placeholder]';
114
+ const existingElements = document.querySelectorAll(selector);
115
+ existingElements.forEach(el => {
116
+ const placeholderId = el instanceof HTMLElement && this.getPlaceholderId(el);
117
+ if (placeholderId && !this.staticPlaceholders.has(placeholderId)) {
118
+ var _window$__SSR_PLACEHO, _this$intersectionObs2;
119
+ let width = -1;
120
+ let height = -1;
121
+ let x = -1;
122
+ let y = -1;
123
+
124
+ // Use SSR dimensions from window global if available
125
+ const boundingClientRect = (_window$__SSR_PLACEHO = window.__SSR_PLACEHOLDERS_DIMENSIONS__) === null || _window$__SSR_PLACEHO === void 0 ? void 0 : _window$__SSR_PLACEHO[placeholderId];
126
+ if (boundingClientRect) {
127
+ width = boundingClientRect.width;
128
+ height = boundingClientRect.height;
129
+ x = boundingClientRect.x;
130
+ y = boundingClientRect.y;
131
+ } else {
132
+ // Fallback to current bounding rect if SSR dimensions not available
133
+ const rect = el.getBoundingClientRect();
134
+ width = rect.width;
135
+ height = rect.height;
136
+ x = rect.x;
137
+ y = rect.y;
138
+ }
139
+ this.staticPlaceholders.set(placeholderId, {
140
+ width,
141
+ height,
142
+ x,
143
+ y
144
+ });
145
+ (_this$intersectionObs2 = this.intersectionObserver) === null || _this$intersectionObs2 === void 0 ? void 0 : _this$intersectionObs2.observe(el);
146
+ }
147
+ });
148
+ }
149
+
150
+ /**
151
+ * Added this method to be utilised for testing purposes.
152
+ * In production it collection placeholder should only happens on constructor
153
+ */
154
+ collectExistingPlaceholders() {
155
+ if (!window.document) {
156
+ return;
157
+ }
158
+ try {
159
+ // Collect placeholders using SSR dimensions or fallback to live dimensions
160
+ this.collectPlaceholdersInternal();
161
+ } catch (e) {
162
+ // Silently fail if there are any issues
163
+ } finally {
164
+ delete window.__SSR_PLACEHOLDERS_DIMENSIONS__;
165
+ }
166
+ }
136
167
  isPlaceholder(element) {
137
168
  return Boolean(this.getPlaceholderId(element));
138
169
  }
@@ -1,5 +1,6 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import { fg } from '@atlaskit/platform-feature-flags';
3
+ import { SSRPlaceholderHandlers } from '../vc-observer/observers/ssr-placeholders';
3
4
  import EntriesTimeline from './entries-timeline';
4
5
  import getElementName from './get-element-name';
5
6
  import VCCalculator_FY25_03 from './metric-calculator/fy25_03';
@@ -7,6 +8,11 @@ import getViewportHeight from './metric-calculator/utils/get-viewport-height';
7
8
  import getViewportWidth from './metric-calculator/utils/get-viewport-width';
8
9
  import ViewportObserver from './viewport-observer';
9
10
  import WindowEventObserver from './window-event-observer';
11
+ const SSRState = {
12
+ normal: 1,
13
+ waitingForFirstRender: 2,
14
+ ignoring: 3
15
+ };
10
16
  const DEFAULT_SELECTOR_CONFIG = {
11
17
  id: false,
12
18
  testId: true,
@@ -19,9 +25,31 @@ export default class VCObserverNew {
19
25
  var _config$isPostInterac, _config$selectorConfi;
20
26
  _defineProperty(this, "viewportObserver", null);
21
27
  _defineProperty(this, "windowEventObserver", null);
28
+ // SSR related properties
29
+ _defineProperty(this, "ssrPlaceholderHandler", null);
30
+ _defineProperty(this, "ssr", {
31
+ state: SSRState.normal,
32
+ reactRootElement: null,
33
+ renderStart: -1,
34
+ renderStop: -1
35
+ });
22
36
  this.entriesTimeline = new EntriesTimeline();
23
37
  this.isPostInteraction = (_config$isPostInterac = config.isPostInteraction) !== null && _config$isPostInterac !== void 0 ? _config$isPostInterac : false;
24
38
  this.selectorConfig = (_config$selectorConfi = config.selectorConfig) !== null && _config$selectorConfi !== void 0 ? _config$selectorConfi : DEFAULT_SELECTOR_CONFIG;
39
+
40
+ // Use shared SSR placeholder handler if provided, otherwise create new one if feature flag is enabled
41
+ if (config.ssrPlaceholderHandler) {
42
+ this.ssrPlaceholderHandler = config.ssrPlaceholderHandler;
43
+ } else {
44
+ var _config$SSRConfig$ena, _config$SSRConfig, _config$SSRConfig$dis, _config$SSRConfig2;
45
+ this.ssrPlaceholderHandler = new SSRPlaceholderHandlers({
46
+ 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,
47
+ disableSizeAndPositionCheck: (_config$SSRConfig$dis = (_config$SSRConfig2 = config.SSRConfig) === null || _config$SSRConfig2 === void 0 ? void 0 : _config$SSRConfig2.disableSizeAndPositionCheck) !== null && _config$SSRConfig$dis !== void 0 ? _config$SSRConfig$dis : {
48
+ v: false,
49
+ h: false
50
+ }
51
+ });
52
+ }
25
53
  this.viewportObserver = new ViewportObserver({
26
54
  onChange: onChangeArg => {
27
55
  const {
@@ -51,7 +79,10 @@ export default class VCObserverNew {
51
79
  newValue: mutationData === null || mutationData === void 0 ? void 0 : mutationData.newValue
52
80
  }
53
81
  });
54
- }
82
+ },
83
+ // Pass SSR context to ViewportObserver
84
+ getSSRState: () => this.getSSRState(),
85
+ getSSRPlaceholderHandler: () => this.getSSRPlaceholderHandler()
55
86
  });
56
87
  this.windowEventObserver = new WindowEventObserver({
57
88
  onEvent: ({
@@ -72,6 +103,14 @@ export default class VCObserverNew {
72
103
  startTime
73
104
  }) {
74
105
  var _this$viewportObserve, _window, _this$windowEventObse;
106
+ // Reset SSR state on start (matches old VCObserver behavior)
107
+ this.ssr = {
108
+ state: SSRState.normal,
109
+ reactRootElement: null,
110
+ // Reset to null (matches old VCObserver)
111
+ renderStart: -1,
112
+ renderStop: -1
113
+ };
75
114
  (_this$viewportObserve = this.viewportObserver) === null || _this$viewportObserve === void 0 ? void 0 : _this$viewportObserve.start();
76
115
  if ((_window = window) !== null && _window !== void 0 && _window.__SSR_ABORT_LISTENERS__ && fg('platform_ufo_vc_observer_new_ssr_abort_listener')) {
77
116
  const abortListeners = window.__SSR_ABORT_LISTENERS__;
@@ -97,6 +136,33 @@ export default class VCObserverNew {
97
136
  var _this$viewportObserve2, _this$windowEventObse2;
98
137
  (_this$viewportObserve2 = this.viewportObserver) === null || _this$viewportObserve2 === void 0 ? void 0 : _this$viewportObserve2.stop();
99
138
  (_this$windowEventObse2 = this.windowEventObserver) === null || _this$windowEventObse2 === void 0 ? void 0 : _this$windowEventObse2.stop();
139
+
140
+ // Clear SSR state on stop (matches old VCObserver behavior)
141
+ this.ssr.reactRootElement = null;
142
+ }
143
+
144
+ // SSR related methods
145
+ setReactRootElement(element) {
146
+ this.ssr.reactRootElement = element;
147
+ }
148
+ setReactRootRenderStart(startTime = performance.now()) {
149
+ this.ssr.renderStart = startTime;
150
+ this.ssr.state = SSRState.waitingForFirstRender;
151
+ }
152
+ setReactRootRenderStop(stopTime = performance.now()) {
153
+ this.ssr.renderStop = stopTime;
154
+ }
155
+ collectSSRPlaceholders() {
156
+ // This is handled by the shared SSRPlaceholderHandlers in VCObserverWrapper
157
+ // Individual observers don't need to implement this
158
+ }
159
+
160
+ // Internal methods for ViewportObserver to access SSR state
161
+ getSSRState() {
162
+ return this.ssr;
163
+ }
164
+ getSSRPlaceholderHandler() {
165
+ return this.ssrPlaceholderHandler;
100
166
  }
101
167
  addSSR(ssr) {
102
168
  this.entriesTimeline.push({
@@ -14,7 +14,7 @@ function isElementVisible(element) {
14
14
  }
15
15
  try {
16
16
  const visible = element.checkVisibility({
17
- // @ts-expect-error
17
+ // @ts-ignore - visibilityProperty may not exist in all TS environments
18
18
  visibilityProperty: true,
19
19
  contentVisibilityAuto: true,
20
20
  opacityProperty: true
@@ -59,8 +59,12 @@ const createElementMutationsWatcher = removedNodeRects => ({
59
59
  return 'mutation:element';
60
60
  };
61
61
  export default class ViewportObserver {
62
+ // SSR context functions
63
+
62
64
  constructor({
63
- onChange
65
+ onChange,
66
+ getSSRState,
67
+ getSSRPlaceholderHandler
64
68
  }) {
65
69
  _defineProperty(this, "handleIntersectionEntry", ({
66
70
  target,
@@ -85,9 +89,11 @@ export default class ViewportObserver {
85
89
  mutationData
86
90
  });
87
91
  });
88
- _defineProperty(this, "handleChildListMutation", ({
92
+ _defineProperty(this, "handleChildListMutation", async ({
93
+ target,
89
94
  addedNodes,
90
- removedNodes
95
+ removedNodes,
96
+ timestamp
91
97
  }) => {
92
98
  const removedNodeRects = removedNodes.map(ref => {
93
99
  const n = ref.deref();
@@ -96,11 +102,66 @@ export default class ViewportObserver {
96
102
  }
97
103
  return this.mapVisibleNodeRects.get(n);
98
104
  });
99
- addedNodes.forEach(addedNodeRef => {
100
- var _this$intersectionObs4;
105
+ const targetNode = target.deref();
106
+ for (const addedNodeRef of addedNodes) {
107
+ var _this$intersectionObs8;
101
108
  const addedNode = addedNodeRef.deref();
102
109
  if (!addedNode) {
103
- return;
110
+ continue;
111
+ }
112
+
113
+ // SSR hydration logic
114
+ if (this.getSSRState && fg('platform_ufo_vc_v3_ssr_placeholder')) {
115
+ const ssrState = this.getSSRState();
116
+ const SSRStateEnum = {
117
+ normal: 1,
118
+ waitingForFirstRender: 2,
119
+ ignoring: 3
120
+ };
121
+ if (ssrState.state === SSRStateEnum.waitingForFirstRender && timestamp > ssrState.renderStart && targetNode === ssrState.reactRootElement) {
122
+ var _this$intersectionObs;
123
+ ssrState.state = SSRStateEnum.ignoring;
124
+ if (ssrState.renderStop === -1) {
125
+ // arbitrary 500ms DOM update window
126
+ ssrState.renderStop = timestamp + 500;
127
+ }
128
+ (_this$intersectionObs = this.intersectionObserver) === null || _this$intersectionObs === void 0 ? void 0 : _this$intersectionObs.watchAndTag(addedNode, 'ssr-hydration');
129
+ continue;
130
+ }
131
+ if (ssrState.state === SSRStateEnum.ignoring && timestamp > ssrState.renderStart && targetNode === ssrState.reactRootElement) {
132
+ if (timestamp <= ssrState.renderStop) {
133
+ var _this$intersectionObs2;
134
+ (_this$intersectionObs2 = this.intersectionObserver) === null || _this$intersectionObs2 === void 0 ? void 0 : _this$intersectionObs2.watchAndTag(addedNode, 'ssr-hydration');
135
+ continue;
136
+ } else {
137
+ ssrState.state = SSRStateEnum.normal;
138
+ }
139
+ }
140
+ }
141
+
142
+ // SSR placeholder logic - check and handle with await
143
+ if (this.getSSRPlaceholderHandler && fg('platform_ufo_vc_v3_ssr_placeholder')) {
144
+ const ssrPlaceholderHandler = this.getSSRPlaceholderHandler();
145
+ if (ssrPlaceholderHandler) {
146
+ if (ssrPlaceholderHandler.isPlaceholder(addedNode) || ssrPlaceholderHandler.isPlaceholderIgnored(addedNode)) {
147
+ const result = await ssrPlaceholderHandler.checkIfExistedAndSizeMatching(addedNode);
148
+ if (result !== false) {
149
+ var _this$intersectionObs3;
150
+ (_this$intersectionObs3 = this.intersectionObserver) === null || _this$intersectionObs3 === void 0 ? void 0 : _this$intersectionObs3.watchAndTag(addedNode, 'mutation:ssr-placeholder');
151
+ continue;
152
+ }
153
+ // If result is false, continue to normal mutation logic below
154
+ }
155
+ if (ssrPlaceholderHandler.isPlaceholderReplacement(addedNode) || ssrPlaceholderHandler.isPlaceholderIgnored(addedNode)) {
156
+ const result = await ssrPlaceholderHandler.validateReactComponentMatchToPlaceholder(addedNode);
157
+ if (result !== false) {
158
+ var _this$intersectionObs4;
159
+ (_this$intersectionObs4 = this.intersectionObserver) === null || _this$intersectionObs4 === void 0 ? void 0 : _this$intersectionObs4.watchAndTag(addedNode, 'mutation:ssr-placeholder');
160
+ continue;
161
+ }
162
+ // If result is false, continue to normal mutation logic below
163
+ }
164
+ }
104
165
  }
105
166
  const sameDeletedNode = removedNodes.find(ref => {
106
167
  const n = ref.deref();
@@ -115,27 +176,27 @@ export default class ViewportObserver {
115
176
  // When fg('platform_vc_ignore_no_ls_mutation_marker') is not enabled,
116
177
  // no layout shift mutation is excluded as per existing fy25.03 logic
117
178
  if (sameDeletedNode && (!isNoLsMarkerEnabled || isInIgnoreLsMarker)) {
118
- var _this$intersectionObs;
119
- (_this$intersectionObs = this.intersectionObserver) === null || _this$intersectionObs === void 0 ? void 0 : _this$intersectionObs.watchAndTag(addedNode, 'mutation:remount');
120
- return;
179
+ var _this$intersectionObs5;
180
+ (_this$intersectionObs5 = this.intersectionObserver) === null || _this$intersectionObs5 === void 0 ? void 0 : _this$intersectionObs5.watchAndTag(addedNode, 'mutation:remount');
181
+ continue;
121
182
  }
122
183
  if (isContainedWithinMediaWrapper(addedNode)) {
123
- var _this$intersectionObs2;
124
- (_this$intersectionObs2 = this.intersectionObserver) === null || _this$intersectionObs2 === void 0 ? void 0 : _this$intersectionObs2.watchAndTag(addedNode, 'mutation:media');
125
- return;
184
+ var _this$intersectionObs6;
185
+ (_this$intersectionObs6 = this.intersectionObserver) === null || _this$intersectionObs6 === void 0 ? void 0 : _this$intersectionObs6.watchAndTag(addedNode, 'mutation:media');
186
+ continue;
126
187
  }
127
188
  const {
128
189
  isWithinThirdPartySegment,
129
190
  ignoredReason
130
191
  } = checkThirdPartySegmentWithIgnoreReason(addedNode);
131
192
  if (isWithinThirdPartySegment) {
132
- var _this$intersectionObs3;
193
+ var _this$intersectionObs7;
133
194
  const assignedReason = createMutationTypeWithIgnoredReason(ignoredReason || 'third-party-element');
134
- (_this$intersectionObs3 = this.intersectionObserver) === null || _this$intersectionObs3 === void 0 ? void 0 : _this$intersectionObs3.watchAndTag(addedNode, assignedReason);
135
- return;
195
+ (_this$intersectionObs7 = this.intersectionObserver) === null || _this$intersectionObs7 === void 0 ? void 0 : _this$intersectionObs7.watchAndTag(addedNode, assignedReason);
196
+ continue;
136
197
  }
137
- (_this$intersectionObs4 = this.intersectionObserver) === null || _this$intersectionObs4 === void 0 ? void 0 : _this$intersectionObs4.watchAndTag(addedNode, createElementMutationsWatcher(removedNodeRects));
138
- });
198
+ (_this$intersectionObs8 = this.intersectionObserver) === null || _this$intersectionObs8 === void 0 ? void 0 : _this$intersectionObs8.watchAndTag(addedNode, createElementMutationsWatcher(removedNodeRects));
199
+ }
139
200
  });
140
201
  _defineProperty(this, "handleAttributeMutation", ({
141
202
  target,
@@ -143,8 +204,8 @@ export default class ViewportObserver {
143
204
  oldValue,
144
205
  newValue
145
206
  }) => {
146
- var _this$intersectionObs5;
147
- (_this$intersectionObs5 = this.intersectionObserver) === null || _this$intersectionObs5 === void 0 ? void 0 : _this$intersectionObs5.watchAndTag(target, ({
207
+ var _this$intersectionObs9;
208
+ (_this$intersectionObs9 = this.intersectionObserver) === null || _this$intersectionObs9 === void 0 ? void 0 : _this$intersectionObs9.watchAndTag(target, ({
148
209
  target,
149
210
  rect
150
211
  }) => {
@@ -243,6 +304,10 @@ export default class ViewportObserver {
243
304
  this.intersectionObserver = null;
244
305
  this.mutationObserver = null;
245
306
  this.performanceObserver = null;
307
+
308
+ // Initialize SSR context functions
309
+ this.getSSRState = getSSRState;
310
+ this.getSSRPlaceholderHandler = getSSRPlaceholderHandler;
246
311
  }
247
312
  initializeObservers() {
248
313
  if (this.isStarted) {
@@ -280,12 +345,12 @@ export default class ViewportObserver {
280
345
  this.isStarted = true;
281
346
  }
282
347
  stop() {
283
- var _this$mutationObserve2, _this$intersectionObs6, _this$performanceObse2;
348
+ var _this$mutationObserve2, _this$intersectionObs0, _this$performanceObse2;
284
349
  if (!this.isStarted) {
285
350
  return;
286
351
  }
287
352
  (_this$mutationObserve2 = this.mutationObserver) === null || _this$mutationObserve2 === void 0 ? void 0 : _this$mutationObserve2.disconnect();
288
- (_this$intersectionObs6 = this.intersectionObserver) === null || _this$intersectionObs6 === void 0 ? void 0 : _this$intersectionObs6.disconnect();
353
+ (_this$intersectionObs0 = this.intersectionObserver) === null || _this$intersectionObs0 === void 0 ? void 0 : _this$intersectionObs0.disconnect();
289
354
  (_this$performanceObse2 = this.performanceObserver) === null || _this$performanceObse2 === void 0 ? void 0 : _this$performanceObse2.disconnect();
290
355
  this.isStarted = false;
291
356
  }
@@ -1,4 +1,5 @@
1
- import { fg } from '@atlaskit/platform-feature-flags';
1
+ // Batched mutation data for performance optimization
2
+
2
3
  function createMutationObserver({
3
4
  onAttributeMutation,
4
5
  onChildListMutation,
@@ -8,10 +9,10 @@ function createMutationObserver({
8
9
  return null;
9
10
  }
10
11
  const mutationObserverCallback = mutations => {
11
- const addedNodes = [];
12
- const removedNodes = [];
13
- const attributeMutations = [];
14
12
  const targets = [];
13
+ // Use nested Maps for O(1) batching performance
14
+ // Short-lived Maps are safe since they're discarded after each callback
15
+ const batchedMutations = new Map();
15
16
  for (const mut of mutations) {
16
17
  if (!(mut.target instanceof HTMLElement)) {
17
18
  continue;
@@ -29,51 +30,66 @@ function createMutationObserver({
29
30
  const oldValue = (_mut$oldValue = mut.oldValue) !== null && _mut$oldValue !== void 0 ? _mut$oldValue : undefined;
30
31
  const newValue = mut.attributeName ? mut.target.getAttribute(mut.attributeName) : undefined;
31
32
  if (oldValue !== newValue) {
32
- if (fg('platform_vc_ignore_no_ls_mutation_marker')) {
33
- var _mut$attributeName;
34
- attributeMutations.push({
35
- target: new WeakRef(mut.target),
36
- attributeName: (_mut$attributeName = mut.attributeName) !== null && _mut$attributeName !== void 0 ? _mut$attributeName : 'unknown',
37
- oldValue,
38
- newValue
39
- });
40
- } else {
41
- var _mut$attributeName2;
42
- onAttributeMutation({
43
- target: mut.target,
44
- attributeName: (_mut$attributeName2 = mut.attributeName) !== null && _mut$attributeName2 !== void 0 ? _mut$attributeName2 : 'unknown',
45
- oldValue,
46
- newValue
47
- });
48
- }
33
+ var _mut$attributeName;
34
+ onAttributeMutation({
35
+ target: mut.target,
36
+ attributeName: (_mut$attributeName = mut.attributeName) !== null && _mut$attributeName !== void 0 ? _mut$attributeName : 'unknown',
37
+ oldValue,
38
+ newValue
39
+ });
49
40
  }
50
41
  continue;
51
42
  } else if (mut.type === 'childList') {
52
43
  var _mut$addedNodes, _mut$removedNodes;
44
+ // In chromium browser MutationRecord has timestamp field, which we should use.
45
+ const timestamp = Math.round(mut.timestamp || performance.now());
46
+
47
+ // Get or create timestamp bucket
48
+ let timestampBucket = batchedMutations.get(timestamp);
49
+ if (!timestampBucket) {
50
+ timestampBucket = new Map();
51
+ batchedMutations.set(timestamp, timestampBucket);
52
+ }
53
+
54
+ // Get or create target batch within timestamp bucket
55
+ let batch = timestampBucket.get(mut.target);
56
+ if (!batch) {
57
+ batch = {
58
+ target: new WeakRef(mut.target),
59
+ addedNodes: [],
60
+ removedNodes: [],
61
+ timestamp
62
+ };
63
+ timestampBucket.set(mut.target, batch);
64
+ }
65
+
66
+ // Accumulate added nodes
53
67
  ((_mut$addedNodes = mut.addedNodes) !== null && _mut$addedNodes !== void 0 ? _mut$addedNodes : []).forEach(node => {
54
68
  if (node instanceof HTMLElement) {
55
- addedNodes.push(new WeakRef(node));
69
+ batch.addedNodes.push(new WeakRef(node));
56
70
  }
57
71
  });
72
+
73
+ // Accumulate removed nodes
58
74
  ((_mut$removedNodes = mut.removedNodes) !== null && _mut$removedNodes !== void 0 ? _mut$removedNodes : []).forEach(node => {
59
75
  if (node instanceof HTMLElement) {
60
- removedNodes.push(new WeakRef(node));
76
+ batch.removedNodes.push(new WeakRef(node));
61
77
  }
62
78
  });
63
79
  }
64
80
  targets.push(mut.target);
65
81
  }
66
- onChildListMutation({
67
- addedNodes,
68
- removedNodes
69
- });
70
- for (const mut of attributeMutations) {
71
- onAttributeMutation({
72
- target: mut.target.deref(),
73
- attributeName: mut.attributeName,
74
- oldValue: mut.oldValue,
75
- newValue: mut.newValue
76
- });
82
+
83
+ // Process all batched childList mutations
84
+ for (const timestampBucket of batchedMutations.values()) {
85
+ for (const batch of timestampBucket.values()) {
86
+ onChildListMutation({
87
+ target: batch.target,
88
+ addedNodes: batch.addedNodes,
89
+ removedNodes: batch.removedNodes,
90
+ timestamp: batch.timestamp
91
+ });
92
+ }
77
93
  }
78
94
  onMutationFinished === null || onMutationFinished === void 0 ? void 0 : onMutationFinished({
79
95
  targets
@@ -1,6 +1,6 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
- import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
2
  import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
3
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
4
4
  import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
5
5
  import _createClass from "@babel/runtime/helpers/createClass";
6
6
  var _process;
@@ -12,19 +12,42 @@ import { VCObserverNOOP } from './no-op-vc-observer';
12
12
  import { VCObserver } from './vc-observer';
13
13
  import VCObserverNew from './vc-observer-new';
14
14
  import { RLLPlaceholderHandlers } from './vc-observer/observers/rll-placeholders';
15
+ import { SSRPlaceholderHandlers } from './vc-observer/observers/ssr-placeholders';
15
16
  export var VCObserverWrapper = /*#__PURE__*/function () {
16
17
  function VCObserverWrapper() {
18
+ var _opts$ssrEnablePageLa, _opts$disableSizeAndP;
17
19
  var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
18
20
  _classCallCheck(this, VCObserverWrapper);
19
21
  this.newVCObserver = null;
20
22
  this.oldVCObserver = null;
23
+
24
+ // Initialize SSR placeholder handler once
25
+ this.ssrPlaceholderHandler = new SSRPlaceholderHandlers({
26
+ enablePageLayoutPlaceholder: (_opts$ssrEnablePageLa = opts.ssrEnablePageLayoutPlaceholder) !== null && _opts$ssrEnablePageLa !== void 0 ? _opts$ssrEnablePageLa : false,
27
+ disableSizeAndPositionCheck: (_opts$disableSizeAndP = opts.disableSizeAndPositionCheck) !== null && _opts$disableSizeAndP !== void 0 ? _opts$disableSizeAndP : {
28
+ v: false,
29
+ h: false
30
+ }
31
+ });
21
32
  if (isVCRevisionEnabled('fy25.03')) {
33
+ var _opts$ssrEnablePageLa2, _opts$disableSizeAndP2;
22
34
  this.newVCObserver = new VCObserverNew({
23
- selectorConfig: opts.selectorConfig
35
+ selectorConfig: opts.selectorConfig,
36
+ isPostInteraction: opts.isPostInteraction,
37
+ SSRConfig: {
38
+ enablePageLayoutPlaceholder: (_opts$ssrEnablePageLa2 = opts.ssrEnablePageLayoutPlaceholder) !== null && _opts$ssrEnablePageLa2 !== void 0 ? _opts$ssrEnablePageLa2 : false,
39
+ disableSizeAndPositionCheck: (_opts$disableSizeAndP2 = opts.disableSizeAndPositionCheck) !== null && _opts$disableSizeAndP2 !== void 0 ? _opts$disableSizeAndP2 : {
40
+ v: false,
41
+ h: false
42
+ }
43
+ },
44
+ ssrPlaceholderHandler: this.ssrPlaceholderHandler
24
45
  });
25
46
  }
26
47
  if (isVCRevisionEnabled('fy25.01') || isVCRevisionEnabled('fy25.02')) {
27
- this.oldVCObserver = new VCObserver(opts);
48
+ this.oldVCObserver = new VCObserver(_objectSpread(_objectSpread({}, opts), {}, {
49
+ ssrPlaceholderHandler: this.ssrPlaceholderHandler
50
+ }));
28
51
  }
29
52
  }
30
53
 
@@ -83,6 +106,8 @@ export var VCObserverWrapper = /*#__PURE__*/function () {
83
106
  (_this$newVCObserver2 = this.newVCObserver) === null || _this$newVCObserver2 === void 0 || _this$newVCObserver2.stop();
84
107
  }
85
108
  RLLPlaceholderHandlers.getInstance().reset();
109
+ // Clear shared SSR placeholder handler
110
+ this.ssrPlaceholderHandler.clear();
86
111
  }
87
112
  }, {
88
113
  key: "getVCRawData",
@@ -156,20 +181,28 @@ export var VCObserverWrapper = /*#__PURE__*/function () {
156
181
  }, {
157
182
  key: "setSSRElement",
158
183
  value: function setSSRElement(element) {
159
- var _this$oldVCObserver5;
184
+ var _this$oldVCObserver5, _this$newVCObserver4;
160
185
  (_this$oldVCObserver5 = this.oldVCObserver) === null || _this$oldVCObserver5 === void 0 || _this$oldVCObserver5.setSSRElement(element);
186
+ (_this$newVCObserver4 = this.newVCObserver) === null || _this$newVCObserver4 === void 0 || _this$newVCObserver4.setReactRootElement(element);
161
187
  }
162
188
  }, {
163
189
  key: "setReactRootRenderStart",
164
190
  value: function setReactRootRenderStart(startTime) {
165
- var _this$oldVCObserver6;
191
+ var _this$oldVCObserver6, _this$newVCObserver5;
166
192
  (_this$oldVCObserver6 = this.oldVCObserver) === null || _this$oldVCObserver6 === void 0 || _this$oldVCObserver6.setReactRootRenderStart(startTime || performance.now());
193
+ (_this$newVCObserver5 = this.newVCObserver) === null || _this$newVCObserver5 === void 0 || _this$newVCObserver5.setReactRootRenderStart(startTime || performance.now());
167
194
  }
168
195
  }, {
169
196
  key: "setReactRootRenderStop",
170
197
  value: function setReactRootRenderStop(stopTime) {
171
- var _this$oldVCObserver7;
198
+ var _this$oldVCObserver7, _this$newVCObserver6;
172
199
  (_this$oldVCObserver7 = this.oldVCObserver) === null || _this$oldVCObserver7 === void 0 || _this$oldVCObserver7.setReactRootRenderStop(stopTime || performance.now());
200
+ (_this$newVCObserver6 = this.newVCObserver) === null || _this$newVCObserver6 === void 0 || _this$newVCObserver6.setReactRootRenderStop(stopTime || performance.now());
201
+ }
202
+ }, {
203
+ key: "collectSSRPlaceholders",
204
+ value: function collectSSRPlaceholders() {
205
+ this.ssrPlaceholderHandler.collectExistingPlaceholders();
173
206
  }
174
207
  }]);
175
208
  }();
@@ -448,7 +448,8 @@ export var VCObserver = /*#__PURE__*/function () {
448
448
  this.devToolsEnabled = options.devToolsEnabled || false;
449
449
  this.oldDomUpdatesEnabled = options.oldDomUpdates || false;
450
450
  var ssrEnablePageLayoutPlaceholder = options.ssrEnablePageLayoutPlaceholder,
451
- disableSizeAndPositionCheck = options.disableSizeAndPositionCheck;
451
+ disableSizeAndPositionCheck = options.disableSizeAndPositionCheck,
452
+ ssrPlaceholderHandler = options.ssrPlaceholderHandler;
452
453
  this.observers = new Observers({
453
454
  selectorConfig: options.selectorConfig || {
454
455
  id: false,
@@ -460,7 +461,8 @@ export var VCObserver = /*#__PURE__*/function () {
460
461
  SSRConfig: {
461
462
  enablePageLayoutPlaceholder: ssrEnablePageLayoutPlaceholder || false,
462
463
  disableSizeAndPositionCheck: disableSizeAndPositionCheck
463
- }
464
+ },
465
+ ssrPlaceholderHandler: ssrPlaceholderHandler
464
466
  });
465
467
  this.heatmap = !isVCRevisionEnabled('fy25.01') ? [] : this.getCleanHeatmap();
466
468
  this.heatmapNext = this.getCleanHeatmap();
@@ -520,6 +522,12 @@ export var VCObserver = /*#__PURE__*/function () {
520
522
  var stopTime = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : performance.now();
521
523
  this.observers.setReactRootRenderStop(stopTime);
522
524
  }
525
+ }, {
526
+ key: "collectSSRPlaceholders",
527
+ value: function collectSSRPlaceholders() {
528
+ // This is handled by the shared SSRPlaceholderHandlers in VCObserverWrapper
529
+ // Individual observers don't need to implement this
530
+ }
523
531
  }, {
524
532
  key: "setAbortReason",
525
533
  value: function setAbortReason(abort, timestamp) {