@atlaskit/renderer 126.7.0 → 126.8.0

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.
@@ -0,0 +1,229 @@
1
+ import { useEffect } from 'react';
2
+ import { getDocument } from '@atlaskit/browser-apis';
3
+ import { DEFAULT_BLOCK_LINK_HASH_PREFIX, expandAllParentsThenScroll, expandElement, isExpandCollapsed, findNodeWithExpandParents, getLocalIdSelector } from '@atlaskit/editor-common/block-menu';
4
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
5
+ import { useStableScroll } from './useStableScroll';
6
+
7
+ /**
8
+ * useScrollToBlock - Handler for block link scrolling in the renderer with expand support
9
+ *
10
+ * This hook enables scroll-to-block functionality when blocks may be hidden inside collapsed expands.
11
+ * It searches the ADF document for the target block, identifies any parent expand nodes,
12
+ * expands them if needed, and waits for layout stability before scrolling to the block.
13
+ *
14
+ * This implementation waits for the container to stabilize (no layout shifts) before scrolling,
15
+ * which prevents issues with images loading, dynamic content, or other async operations that
16
+ * cause layout changes.
17
+ *
18
+ * This hook replaces useScrollToLocalId when the platform_editor_expand_on_scroll_to_block experiment is enabled.
19
+ *
20
+ * When platform_editor_expand_on_scroll_to_block experiment is cleaned up:
21
+ * - Remove the experiment check
22
+ * - Delete the deprecated useScrollToLocalId hook
23
+ * - Make this the default scroll-to-block behavior
24
+ *
25
+ * @param containerRef - Optional ref to the renderer container (RendererStyleContainer)
26
+ * @param adfDoc - The ADF document to search for nodes and expand parents
27
+ */
28
+ export const useScrollToBlock = (containerRef, adfDoc) => {
29
+ const {
30
+ waitForStability,
31
+ cleanup: cleanupStability
32
+ } = useStableScroll({
33
+ stabilityWaitTime: 750,
34
+ maxStabilityWaitTime: 10_000
35
+ });
36
+ useEffect(() => {
37
+ var _getDocument;
38
+ // Only run in browser environment.
39
+ if (typeof window === 'undefined' || !(containerRef !== null && containerRef !== void 0 && containerRef.current)) {
40
+ return;
41
+ }
42
+ if (!expValEquals('platform_editor_expand_on_scroll_to_block', 'isEnabled', true)) {
43
+ return;
44
+ }
45
+
46
+ // Parse hash fragment for block ID (format: #block-{localId}).
47
+ const hash = window.location.hash;
48
+ const defaultPrefixWithHash = `#${DEFAULT_BLOCK_LINK_HASH_PREFIX}`;
49
+ const blockId = hash.startsWith(defaultPrefixWithHash) ? hash.slice(defaultPrefixWithHash.length) : null;
50
+ if (!blockId) {
51
+ return;
52
+ }
53
+ let retryCount = 0;
54
+ const maxRetries = 40;
55
+ const retryInterval = 250;
56
+ let intervalId = null;
57
+ let hasScrolled = false;
58
+ let cancelExpandAndScroll = null;
59
+ const scrollToElement = () => {
60
+ // Step 1: Search the ADF document for the node with the given blockId.
61
+ // This works even if the node is hidden inside a collapsed expand.
62
+ if (!adfDoc || !(containerRef !== null && containerRef !== void 0 && containerRef.current)) {
63
+ return false;
64
+ }
65
+ const nodeWithExpandParents = findNodeWithExpandParents(adfDoc, blockId);
66
+ if (!nodeWithExpandParents) {
67
+ // Node not found in ADF document.
68
+ return false;
69
+ }
70
+ const {
71
+ expandParentLocalIds
72
+ } = nodeWithExpandParents;
73
+
74
+ // Step 2: If the node has expand parents, we need to expand them first.
75
+ if (expandParentLocalIds.length > 0) {
76
+ // Find the expand elements in the DOM using their localIds.
77
+ // Note: We need to expand from outermost to innermost.
78
+ let allExpandsFound = true;
79
+ let anyExpandsCollapsed = false;
80
+ for (const expandLocalId of expandParentLocalIds) {
81
+ const expandContainer = containerRef.current.querySelector(`[data-local-id="${expandLocalId}"]`);
82
+ if (!expandContainer) {
83
+ // Expand not found in DOM yet (shouldn't happen but handle it).
84
+ allExpandsFound = false;
85
+ break;
86
+ }
87
+
88
+ // Check if this expand is collapsed.
89
+ if (isExpandCollapsed(expandContainer)) {
90
+ anyExpandsCollapsed = true;
91
+ // Expand it.
92
+ expandElement(expandContainer);
93
+ // After expanding, we need to retry to handle nested expands.
94
+ // The DOM needs time to update.
95
+ return false; // Will retry after interval.
96
+ }
97
+ }
98
+ if (!allExpandsFound) {
99
+ // Retry later when expands are in DOM.
100
+ return false;
101
+ }
102
+
103
+ // All parent expands are now open (or we just expanded one and need to wait).
104
+ if (anyExpandsCollapsed) {
105
+ // Just expanded something, wait for DOM update.
106
+ return false;
107
+ }
108
+ }
109
+
110
+ // Step 3: Now the target element should be visible in the DOM, find it and scroll.
111
+ const element = getLocalIdSelector(blockId, containerRef.current);
112
+ if (!element) {
113
+ // Element still not in DOM, retry.
114
+ return false;
115
+ }
116
+
117
+ // Element found and all parent expands are open! Use the utility to scroll.
118
+ // (This will handle any final edge cases and do the actual scrolling).
119
+ // Capture cleanup function to cancel pending timeouts.
120
+ cancelExpandAndScroll = expandAllParentsThenScroll(element);
121
+ return true;
122
+ };
123
+ const performScroll = () => {
124
+ if (hasScrolled) {
125
+ return;
126
+ }
127
+
128
+ // Try to scroll to element.
129
+ if (scrollToElement()) {
130
+ hasScrolled = true;
131
+ cleanup();
132
+ }
133
+ };
134
+ const attemptScroll = () => {
135
+ retryCount++;
136
+
137
+ // Try to find the element first.
138
+ if (!adfDoc || !(containerRef !== null && containerRef !== void 0 && containerRef.current)) {
139
+ return false;
140
+ }
141
+ const nodeWithExpandParents = findNodeWithExpandParents(adfDoc, blockId);
142
+ if (!nodeWithExpandParents) {
143
+ return false;
144
+ }
145
+ const {
146
+ expandParentLocalIds
147
+ } = nodeWithExpandParents;
148
+
149
+ // Check if all expands are expanded and element exists.
150
+ let allReady = true;
151
+ if (expandParentLocalIds.length > 0) {
152
+ for (const expandLocalId of expandParentLocalIds) {
153
+ const expandContainer = containerRef.current.querySelector(`[data-local-id="${expandLocalId}"]`);
154
+ if (!expandContainer) {
155
+ allReady = false;
156
+ break;
157
+ }
158
+ if (isExpandCollapsed(expandContainer)) {
159
+ expandElement(expandContainer);
160
+ allReady = false;
161
+ break;
162
+ }
163
+ }
164
+ }
165
+ const element = getLocalIdSelector(blockId, containerRef.current);
166
+ if (!element) {
167
+ allReady = false;
168
+ }
169
+
170
+ // If everything is ready, start monitoring for stability.
171
+ if (allReady) {
172
+ if (intervalId) {
173
+ clearInterval(intervalId);
174
+ intervalId = null;
175
+ }
176
+ waitForStability(containerRef.current, performScroll);
177
+ return true;
178
+ }
179
+
180
+ // Stop retrying if we've exceeded max retries.
181
+ if (retryCount >= maxRetries) {
182
+ cleanup();
183
+ return false;
184
+ }
185
+ return false;
186
+ };
187
+ const cleanup = () => {
188
+ if (intervalId) {
189
+ clearInterval(intervalId);
190
+ intervalId = null;
191
+ }
192
+ cleanupStability();
193
+ // Cancel any pending expand and scroll operations.
194
+ if (cancelExpandAndScroll) {
195
+ cancelExpandAndScroll();
196
+ cancelExpandAndScroll = null;
197
+ }
198
+ };
199
+
200
+ // Try to scroll immediately.
201
+ if (attemptScroll()) {
202
+ return cleanup;
203
+ }
204
+ if (((_getDocument = getDocument()) === null || _getDocument === void 0 ? void 0 : _getDocument.readyState) === 'complete') {
205
+ // Document is already ready, try a few more times with delays.
206
+ // This handles cases where elements are added after document ready.
207
+ intervalId = setInterval(() => {
208
+ attemptScroll();
209
+ }, retryInterval);
210
+ } else {
211
+ // Document not ready yet, wait for it and then retry.
212
+ intervalId = setInterval(() => {
213
+ var _getDocument2;
214
+ if (((_getDocument2 = getDocument()) === null || _getDocument2 === void 0 ? void 0 : _getDocument2.readyState) === 'complete') {
215
+ attemptScroll();
216
+ } else if (retryCount >= maxRetries) {
217
+ cleanup();
218
+ } else {
219
+ retryCount++;
220
+ }
221
+ }, retryInterval);
222
+ }
223
+
224
+ // Cleanup function.
225
+ return cleanup;
226
+ // Intentionally not including adfDoc in the dependency array to avoid unnecessary re-renders.
227
+ // eslint-disable-next-line react-hooks/exhaustive-deps
228
+ }, [containerRef, waitForStability, cleanupStability]);
229
+ };
@@ -1,6 +1,7 @@
1
1
  import { useEffect } from 'react';
2
2
  import { getDocument } from '@atlaskit/browser-apis';
3
3
  import { DEFAULT_BLOCK_LINK_HASH_PREFIX } from '@atlaskit/editor-common/block-menu';
4
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
4
5
 
5
6
  // Find editor node dom with localId - similar to confluence useScrollOnUrlChange.ts
6
7
  const getLocalIdSelector = (localId, container) => {
@@ -33,6 +34,16 @@ const getLocalIdSelector = (localId, container) => {
33
34
  }
34
35
  return null;
35
36
  };
37
+
38
+ /**
39
+ * useScrollToLocalId - Handler for block link scrolling in the renderer (traditional pages)
40
+ *
41
+ * This hook is deprecated in favor of useScrollToBlock which supports expanding parent nodes.
42
+ * This hook will be removed when the platform_editor_expand_on_scroll_to_block experiment is cleaned up.
43
+ *
44
+ * @param containerRef - Optional ref to the renderer container (RendererStyleContainer).
45
+ * @param shouldScrollToLocalId - Whether scroll-to-block functionality should be enabled
46
+ */
36
47
  export const useScrollToLocalId = (containerRef, shouldScrollToLocalId) => {
37
48
  useEffect(() => {
38
49
  var _getDocument;
@@ -40,6 +51,9 @@ export const useScrollToLocalId = (containerRef, shouldScrollToLocalId) => {
40
51
  if (typeof window === 'undefined' || !(containerRef !== null && containerRef !== void 0 && containerRef.current) || !shouldScrollToLocalId) {
41
52
  return;
42
53
  }
54
+ if (expValEquals('platform_editor_expand_on_scroll_to_block', 'isEnabled', true)) {
55
+ return;
56
+ }
43
57
 
44
58
  // Parse hash fragment for block ID (format: #block-{localId})
45
59
  const hash = window.location.hash;
@@ -0,0 +1,88 @@
1
+ import { useRef, useCallback } from 'react';
2
+ /**
3
+ * Hook that provides functionality to wait for layout stability before performing an action.
4
+ * Uses ResizeObserver to detect when a container has stopped resizing (e.g., images finished loading).
5
+ */
6
+ export const useStableScroll = (options = {}) => {
7
+ const {
8
+ stabilityWaitTime = 200,
9
+ maxStabilityWaitTime = 10_000
10
+ } = options;
11
+ const stabilityTimeoutRef = useRef(null);
12
+ const resizeObserverRef = useRef(null);
13
+ const lastStableTimeRef = useRef(0);
14
+ const onStableCallbackRef = useRef(null);
15
+ const cleanup = useCallback(() => {
16
+ if (stabilityTimeoutRef.current) {
17
+ clearTimeout(stabilityTimeoutRef.current);
18
+ stabilityTimeoutRef.current = null;
19
+ }
20
+ if (resizeObserverRef.current) {
21
+ resizeObserverRef.current.disconnect();
22
+ resizeObserverRef.current = null;
23
+ }
24
+ onStableCallbackRef.current = null;
25
+ lastStableTimeRef.current = 0;
26
+ }, []);
27
+ const scheduleStabilityCheck = useCallback(() => {
28
+ // Clear any existing stability timeout.
29
+ if (stabilityTimeoutRef.current) {
30
+ clearTimeout(stabilityTimeoutRef.current);
31
+ stabilityTimeoutRef.current = null;
32
+ }
33
+
34
+ // Check if we've exceeded the maximum stability wait time.
35
+ const now = Date.now();
36
+ if (lastStableTimeRef.current === 0) {
37
+ lastStableTimeRef.current = now;
38
+ } else if (now - lastStableTimeRef.current > maxStabilityWaitTime) {
39
+ // We've waited too long for stability, call the callback now.
40
+ if (onStableCallbackRef.current) {
41
+ onStableCallbackRef.current();
42
+ cleanup();
43
+ }
44
+ return;
45
+ }
46
+
47
+ // Set a timeout to call the callback after the stability wait time.
48
+ stabilityTimeoutRef.current = setTimeout(() => {
49
+ if (onStableCallbackRef.current) {
50
+ onStableCallbackRef.current();
51
+ cleanup();
52
+ }
53
+ }, stabilityWaitTime);
54
+ }, [stabilityWaitTime, maxStabilityWaitTime, cleanup]);
55
+ const waitForStability = useCallback((container, onStable) => {
56
+ // Clean up any existing observer
57
+ cleanup();
58
+
59
+ // Store the callback
60
+ onStableCallbackRef.current = onStable;
61
+
62
+ // Check if ResizeObserver is available
63
+ if (typeof ResizeObserver === 'undefined') {
64
+ // Fallback: just call the callback after the stability wait time
65
+ stabilityTimeoutRef.current = setTimeout(() => {
66
+ if (onStableCallbackRef.current) {
67
+ onStableCallbackRef.current();
68
+ cleanup();
69
+ }
70
+ }, stabilityWaitTime);
71
+ return;
72
+ }
73
+
74
+ // Create a ResizeObserver to monitor the container for size changes.
75
+ resizeObserverRef.current = new ResizeObserver(() => {
76
+ // Container size changed, reset stability timer.
77
+ scheduleStabilityCheck();
78
+ });
79
+ resizeObserverRef.current.observe(container);
80
+
81
+ // Start the initial stability check
82
+ scheduleStabilityCheck();
83
+ }, [stabilityWaitTime, scheduleStabilityCheck, cleanup]);
84
+ return {
85
+ waitForStability,
86
+ cleanup
87
+ };
88
+ };
@@ -20,6 +20,7 @@ import { browser as browserLegacy, getBrowserInfo } from '@atlaskit/editor-commo
20
20
  import { startMeasure, stopMeasure } from '@atlaskit/editor-common/performance-measures';
21
21
  import { getDistortedDurationMonitor } from '@atlaskit/editor-common/performance/measure-render';
22
22
  import { getResponseEndTime } from '@atlaskit/editor-common/performance/navigation';
23
+ import { useScrollToBlock } from '../hooks/useScrollToBlock';
23
24
  import { getAnalyticsAppearance, getAnalyticsEventSeverity, shouldForceTracking } from '@atlaskit/editor-common/utils';
24
25
  import { fg } from '@atlaskit/platform-feature-flags';
25
26
  import { FabricChannel } from '@atlaskit/analytics-listeners/types';
@@ -61,7 +62,7 @@ export var DEGRADED_SEVERITY_THRESHOLD = 3000;
61
62
  var TABLE_INFO_TIMEOUT = 10000;
62
63
  var RENDER_EVENT_SAMPLE_RATE = 0.2;
63
64
  var packageName = "@atlaskit/renderer";
64
- var packageVersion = "0.0.0-development";
65
+ var packageVersion = "126.7.1";
65
66
  var setAsQueryContainerStyles = css({
66
67
  containerName: 'ak-renderer-wrapper',
67
68
  containerType: 'inline-size'
@@ -183,7 +184,8 @@ export var RendererFunctionalComponent = function RendererFunctionalComponent(pr
183
184
  return props.dataProviders || new ProviderFactory();
184
185
  }, [props.dataProviders]);
185
186
  var _useRendererContext = useRendererContext(),
186
- parentContextContentMode = _useRendererContext.contentMode;
187
+ parentContextContentMode = _useRendererContext.contentMode,
188
+ nestedRendererType = _useRendererContext.nestedRendererType;
187
189
  var createRendererContext = useMemo(function () {
188
190
  return function (featureFlags, isTopLevelRenderer, contentMode) {
189
191
  var normalizedFeatureFlags = normalizeFeatureFlags(featureFlags);
@@ -353,6 +355,7 @@ export var RendererFunctionalComponent = function RendererFunctionalComponent(pr
353
355
  distortedDuration: renderedMeasurementDistortedDurationMonitor.distortedDuration,
354
356
  ttfb: getResponseEndTime(),
355
357
  nodes: countNodes(props.document),
358
+ nestedRendererType: editorExperiment('platform_synced_block', true) && fg('platform_synced_block_patch_1') ? nestedRendererType : undefined,
356
359
  severity: severity
357
360
  },
358
361
  eventType: EVENT_TYPE.OPERATIONAL
@@ -428,6 +431,7 @@ export var RendererFunctionalComponent = function RendererFunctionalComponent(pr
428
431
  var rendererContext = useMemo(function () {
429
432
  return createRendererContext(props.featureFlags, props.isTopLevelRenderer, props.contentMode);
430
433
  }, [props.featureFlags, props.isTopLevelRenderer, createRendererContext, props.contentMode]);
434
+ useScrollToBlock(editorRef, props.document);
431
435
  try {
432
436
  var _rendererContext$feat, _props$media;
433
437
  var schema = getSchema(props.schema, props.adfStage);
@@ -0,0 +1,245 @@
1
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
2
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
3
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
4
+ import { useEffect } from 'react';
5
+ import { getDocument } from '@atlaskit/browser-apis';
6
+ import { DEFAULT_BLOCK_LINK_HASH_PREFIX, expandAllParentsThenScroll, expandElement, isExpandCollapsed, findNodeWithExpandParents, getLocalIdSelector } from '@atlaskit/editor-common/block-menu';
7
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
8
+ import { useStableScroll } from './useStableScroll';
9
+
10
+ /**
11
+ * useScrollToBlock - Handler for block link scrolling in the renderer with expand support
12
+ *
13
+ * This hook enables scroll-to-block functionality when blocks may be hidden inside collapsed expands.
14
+ * It searches the ADF document for the target block, identifies any parent expand nodes,
15
+ * expands them if needed, and waits for layout stability before scrolling to the block.
16
+ *
17
+ * This implementation waits for the container to stabilize (no layout shifts) before scrolling,
18
+ * which prevents issues with images loading, dynamic content, or other async operations that
19
+ * cause layout changes.
20
+ *
21
+ * This hook replaces useScrollToLocalId when the platform_editor_expand_on_scroll_to_block experiment is enabled.
22
+ *
23
+ * When platform_editor_expand_on_scroll_to_block experiment is cleaned up:
24
+ * - Remove the experiment check
25
+ * - Delete the deprecated useScrollToLocalId hook
26
+ * - Make this the default scroll-to-block behavior
27
+ *
28
+ * @param containerRef - Optional ref to the renderer container (RendererStyleContainer)
29
+ * @param adfDoc - The ADF document to search for nodes and expand parents
30
+ */
31
+ export var useScrollToBlock = function useScrollToBlock(containerRef, adfDoc) {
32
+ var _useStableScroll = useStableScroll({
33
+ stabilityWaitTime: 750,
34
+ maxStabilityWaitTime: 10000
35
+ }),
36
+ waitForStability = _useStableScroll.waitForStability,
37
+ cleanupStability = _useStableScroll.cleanup;
38
+ useEffect(function () {
39
+ var _getDocument;
40
+ // Only run in browser environment.
41
+ if (typeof window === 'undefined' || !(containerRef !== null && containerRef !== void 0 && containerRef.current)) {
42
+ return;
43
+ }
44
+ if (!expValEquals('platform_editor_expand_on_scroll_to_block', 'isEnabled', true)) {
45
+ return;
46
+ }
47
+
48
+ // Parse hash fragment for block ID (format: #block-{localId}).
49
+ var hash = window.location.hash;
50
+ var defaultPrefixWithHash = "#".concat(DEFAULT_BLOCK_LINK_HASH_PREFIX);
51
+ var blockId = hash.startsWith(defaultPrefixWithHash) ? hash.slice(defaultPrefixWithHash.length) : null;
52
+ if (!blockId) {
53
+ return;
54
+ }
55
+ var retryCount = 0;
56
+ var maxRetries = 40;
57
+ var retryInterval = 250;
58
+ var intervalId = null;
59
+ var hasScrolled = false;
60
+ var cancelExpandAndScroll = null;
61
+ var scrollToElement = function scrollToElement() {
62
+ // Step 1: Search the ADF document for the node with the given blockId.
63
+ // This works even if the node is hidden inside a collapsed expand.
64
+ if (!adfDoc || !(containerRef !== null && containerRef !== void 0 && containerRef.current)) {
65
+ return false;
66
+ }
67
+ var nodeWithExpandParents = findNodeWithExpandParents(adfDoc, blockId);
68
+ if (!nodeWithExpandParents) {
69
+ // Node not found in ADF document.
70
+ return false;
71
+ }
72
+ var expandParentLocalIds = nodeWithExpandParents.expandParentLocalIds;
73
+
74
+ // Step 2: If the node has expand parents, we need to expand them first.
75
+ if (expandParentLocalIds.length > 0) {
76
+ // Find the expand elements in the DOM using their localIds.
77
+ // Note: We need to expand from outermost to innermost.
78
+ var allExpandsFound = true;
79
+ var anyExpandsCollapsed = false;
80
+ var _iterator = _createForOfIteratorHelper(expandParentLocalIds),
81
+ _step;
82
+ try {
83
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
84
+ var expandLocalId = _step.value;
85
+ var expandContainer = containerRef.current.querySelector("[data-local-id=\"".concat(expandLocalId, "\"]"));
86
+ if (!expandContainer) {
87
+ // Expand not found in DOM yet (shouldn't happen but handle it).
88
+ allExpandsFound = false;
89
+ break;
90
+ }
91
+
92
+ // Check if this expand is collapsed.
93
+ if (isExpandCollapsed(expandContainer)) {
94
+ anyExpandsCollapsed = true;
95
+ // Expand it.
96
+ expandElement(expandContainer);
97
+ // After expanding, we need to retry to handle nested expands.
98
+ // The DOM needs time to update.
99
+ return false; // Will retry after interval.
100
+ }
101
+ }
102
+ } catch (err) {
103
+ _iterator.e(err);
104
+ } finally {
105
+ _iterator.f();
106
+ }
107
+ if (!allExpandsFound) {
108
+ // Retry later when expands are in DOM.
109
+ return false;
110
+ }
111
+
112
+ // All parent expands are now open (or we just expanded one and need to wait).
113
+ if (anyExpandsCollapsed) {
114
+ // Just expanded something, wait for DOM update.
115
+ return false;
116
+ }
117
+ }
118
+
119
+ // Step 3: Now the target element should be visible in the DOM, find it and scroll.
120
+ var element = getLocalIdSelector(blockId, containerRef.current);
121
+ if (!element) {
122
+ // Element still not in DOM, retry.
123
+ return false;
124
+ }
125
+
126
+ // Element found and all parent expands are open! Use the utility to scroll.
127
+ // (This will handle any final edge cases and do the actual scrolling).
128
+ // Capture cleanup function to cancel pending timeouts.
129
+ cancelExpandAndScroll = expandAllParentsThenScroll(element);
130
+ return true;
131
+ };
132
+ var performScroll = function performScroll() {
133
+ if (hasScrolled) {
134
+ return;
135
+ }
136
+
137
+ // Try to scroll to element.
138
+ if (scrollToElement()) {
139
+ hasScrolled = true;
140
+ cleanup();
141
+ }
142
+ };
143
+ var attemptScroll = function attemptScroll() {
144
+ retryCount++;
145
+
146
+ // Try to find the element first.
147
+ if (!adfDoc || !(containerRef !== null && containerRef !== void 0 && containerRef.current)) {
148
+ return false;
149
+ }
150
+ var nodeWithExpandParents = findNodeWithExpandParents(adfDoc, blockId);
151
+ if (!nodeWithExpandParents) {
152
+ return false;
153
+ }
154
+ var expandParentLocalIds = nodeWithExpandParents.expandParentLocalIds;
155
+
156
+ // Check if all expands are expanded and element exists.
157
+ var allReady = true;
158
+ if (expandParentLocalIds.length > 0) {
159
+ var _iterator2 = _createForOfIteratorHelper(expandParentLocalIds),
160
+ _step2;
161
+ try {
162
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
163
+ var expandLocalId = _step2.value;
164
+ var expandContainer = containerRef.current.querySelector("[data-local-id=\"".concat(expandLocalId, "\"]"));
165
+ if (!expandContainer) {
166
+ allReady = false;
167
+ break;
168
+ }
169
+ if (isExpandCollapsed(expandContainer)) {
170
+ expandElement(expandContainer);
171
+ allReady = false;
172
+ break;
173
+ }
174
+ }
175
+ } catch (err) {
176
+ _iterator2.e(err);
177
+ } finally {
178
+ _iterator2.f();
179
+ }
180
+ }
181
+ var element = getLocalIdSelector(blockId, containerRef.current);
182
+ if (!element) {
183
+ allReady = false;
184
+ }
185
+
186
+ // If everything is ready, start monitoring for stability.
187
+ if (allReady) {
188
+ if (intervalId) {
189
+ clearInterval(intervalId);
190
+ intervalId = null;
191
+ }
192
+ waitForStability(containerRef.current, performScroll);
193
+ return true;
194
+ }
195
+
196
+ // Stop retrying if we've exceeded max retries.
197
+ if (retryCount >= maxRetries) {
198
+ cleanup();
199
+ return false;
200
+ }
201
+ return false;
202
+ };
203
+ var cleanup = function cleanup() {
204
+ if (intervalId) {
205
+ clearInterval(intervalId);
206
+ intervalId = null;
207
+ }
208
+ cleanupStability();
209
+ // Cancel any pending expand and scroll operations.
210
+ if (cancelExpandAndScroll) {
211
+ cancelExpandAndScroll();
212
+ cancelExpandAndScroll = null;
213
+ }
214
+ };
215
+
216
+ // Try to scroll immediately.
217
+ if (attemptScroll()) {
218
+ return cleanup;
219
+ }
220
+ if (((_getDocument = getDocument()) === null || _getDocument === void 0 ? void 0 : _getDocument.readyState) === 'complete') {
221
+ // Document is already ready, try a few more times with delays.
222
+ // This handles cases where elements are added after document ready.
223
+ intervalId = setInterval(function () {
224
+ attemptScroll();
225
+ }, retryInterval);
226
+ } else {
227
+ // Document not ready yet, wait for it and then retry.
228
+ intervalId = setInterval(function () {
229
+ var _getDocument2;
230
+ if (((_getDocument2 = getDocument()) === null || _getDocument2 === void 0 ? void 0 : _getDocument2.readyState) === 'complete') {
231
+ attemptScroll();
232
+ } else if (retryCount >= maxRetries) {
233
+ cleanup();
234
+ } else {
235
+ retryCount++;
236
+ }
237
+ }, retryInterval);
238
+ }
239
+
240
+ // Cleanup function.
241
+ return cleanup;
242
+ // Intentionally not including adfDoc in the dependency array to avoid unnecessary re-renders.
243
+ // eslint-disable-next-line react-hooks/exhaustive-deps
244
+ }, [containerRef, waitForStability, cleanupStability]);
245
+ };
@@ -1,6 +1,7 @@
1
1
  import { useEffect } from 'react';
2
2
  import { getDocument } from '@atlaskit/browser-apis';
3
3
  import { DEFAULT_BLOCK_LINK_HASH_PREFIX } from '@atlaskit/editor-common/block-menu';
4
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
4
5
 
5
6
  // Find editor node dom with localId - similar to confluence useScrollOnUrlChange.ts
6
7
  var getLocalIdSelector = function getLocalIdSelector(localId, container) {
@@ -33,6 +34,16 @@ var getLocalIdSelector = function getLocalIdSelector(localId, container) {
33
34
  }
34
35
  return null;
35
36
  };
37
+
38
+ /**
39
+ * useScrollToLocalId - Handler for block link scrolling in the renderer (traditional pages)
40
+ *
41
+ * This hook is deprecated in favor of useScrollToBlock which supports expanding parent nodes.
42
+ * This hook will be removed when the platform_editor_expand_on_scroll_to_block experiment is cleaned up.
43
+ *
44
+ * @param containerRef - Optional ref to the renderer container (RendererStyleContainer).
45
+ * @param shouldScrollToLocalId - Whether scroll-to-block functionality should be enabled
46
+ */
36
47
  export var useScrollToLocalId = function useScrollToLocalId(containerRef, shouldScrollToLocalId) {
37
48
  useEffect(function () {
38
49
  var _getDocument;
@@ -40,6 +51,9 @@ export var useScrollToLocalId = function useScrollToLocalId(containerRef, should
40
51
  if (typeof window === 'undefined' || !(containerRef !== null && containerRef !== void 0 && containerRef.current) || !shouldScrollToLocalId) {
41
52
  return;
42
53
  }
54
+ if (expValEquals('platform_editor_expand_on_scroll_to_block', 'isEnabled', true)) {
55
+ return;
56
+ }
43
57
 
44
58
  // Parse hash fragment for block ID (format: #block-{localId})
45
59
  var hash = window.location.hash;