@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.
- package/CHANGELOG.md +26 -0
- package/dist/cjs/ui/Renderer/index.js +6 -2
- package/dist/cjs/ui/hooks/useScrollToBlock.js +250 -0
- package/dist/cjs/ui/hooks/useScrollToLocalId.js +14 -0
- package/dist/cjs/ui/hooks/useStableScroll.js +95 -0
- package/dist/es2019/ui/Renderer/index.js +6 -2
- package/dist/es2019/ui/hooks/useScrollToBlock.js +229 -0
- package/dist/es2019/ui/hooks/useScrollToLocalId.js +14 -0
- package/dist/es2019/ui/hooks/useStableScroll.js +88 -0
- package/dist/esm/ui/Renderer/index.js +6 -2
- package/dist/esm/ui/hooks/useScrollToBlock.js +245 -0
- package/dist/esm/ui/hooks/useScrollToLocalId.js +14 -0
- package/dist/esm/ui/hooks/useStableScroll.js +89 -0
- package/dist/types/analytics/events.d.ts +3 -0
- package/dist/types/renderer-context.d.ts +2 -1
- package/dist/types/ui/Renderer/types.d.ts +1 -0
- package/dist/types/ui/hooks/useScrollToBlock.d.ts +23 -0
- package/dist/types/ui/hooks/useScrollToLocalId.d.ts +9 -0
- package/dist/types/ui/hooks/useStableScroll.d.ts +27 -0
- package/dist/types-ts4.5/analytics/events.d.ts +3 -0
- package/dist/types-ts4.5/renderer-context.d.ts +2 -1
- package/dist/types-ts4.5/ui/Renderer/types.d.ts +1 -0
- package/dist/types-ts4.5/ui/hooks/useScrollToBlock.d.ts +23 -0
- package/dist/types-ts4.5/ui/hooks/useScrollToLocalId.d.ts +9 -0
- package/dist/types-ts4.5/ui/hooks/useStableScroll.d.ts +27 -0
- package/package.json +5 -2
|
@@ -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 = "
|
|
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;
|