Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Scroll to previous location in document with editor back button
  • Loading branch information
tellthemachines committed Dec 9, 2025
commit e01cbd6a1b27887abc9d286b5a031ba31d8547c2
55 changes: 51 additions & 4 deletions packages/edit-post/src/hooks/use-navigate-to-entity-record.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,15 @@ export default function useNavigateToEntityRecord(
defaultRenderingMode
) {
const [ postHistory, dispatch ] = useReducer(
( historyState, { type, post, previousRenderingMode } ) => {
(
historyState,
{ type, post, previousRenderingMode, scrollPosition }
) => {
if ( type === 'push' ) {
return [ ...historyState, { post, previousRenderingMode } ];
return [
...historyState,
{ post, previousRenderingMode, scrollPosition },
];
}
if ( type === 'pop' ) {
// Try to leave one item in the history.
Expand All @@ -45,19 +51,32 @@ export default function useNavigateToEntityRecord(
]
);

const { post, previousRenderingMode } =
const { post, previousRenderingMode, scrollPosition } =
postHistory[ postHistory.length - 1 ];

const { getRenderingMode } = useSelect( editorStore );
const { setRenderingMode } = useDispatch( editorStore );

const onNavigateToEntityRecord = useCallback(
( params ) => {
// Capture current scroll position from the editor canvas iframe
const iframe = document.querySelector(
'iframe[name="editor-canvas"]'
);
const iframeDocument =
iframe?.contentDocument || iframe?.contentWindow?.document;
const scrollElement = iframeDocument?.documentElement;

const currentScrollPosition = scrollElement
? { x: scrollElement.scrollLeft, y: scrollElement.scrollTop }
: { x: window.scrollX, y: window.scrollY };

dispatch( {
type: 'push',
post: { postId: params.postId, postType: params.postType },
// Save the current rendering mode so we can restore it when navigating back.
previousRenderingMode: getRenderingMode(),
scrollPosition: currentScrollPosition,
} );
setRenderingMode( defaultRenderingMode );
},
Expand All @@ -69,7 +88,35 @@ export default function useNavigateToEntityRecord(
if ( previousRenderingMode ) {
setRenderingMode( previousRenderingMode );
}
}, [ setRenderingMode, previousRenderingMode ] );
// Restore scroll position after a short delay to allow rendering to complete
if ( scrollPosition && typeof window !== 'undefined' ) {
const restoreScroll = () => {
// The editor canvas is inside an iframe
const iframe = document.querySelector(
'iframe[name="editor-canvas"]'
);
const iframeWindow = iframe?.contentWindow;

if ( iframeWindow ) {
// Use a small delay to ensure content is rendered
setTimeout( () => {
iframeWindow.scrollTo(
scrollPosition.x,
scrollPosition.y
);
}, 100 );
} else {
window.scrollTo( scrollPosition.x, scrollPosition.y );
}
};

if ( typeof window.requestAnimationFrame !== 'undefined' ) {
window.requestAnimationFrame( restoreScroll );
} else {
restoreScroll();
}
}
}, [ setRenderingMode, previousRenderingMode, scrollPosition ] );

return {
currentPost: post,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,40 @@
* WordPress dependencies
*/
import { privateApis as routerPrivateApis } from '@wordpress/router';
import { useCallback } from '@wordpress/element';
import { useCallback, useEffect } from '@wordpress/element';

/**
* Internal dependencies
*/
import { unlock } from '../../lock-unlock';

const { useHistory } = unlock( routerPrivateApis );
const { useHistory, useLocation } = unlock( routerPrivateApis );

export default function useNavigateToEntityRecord() {
const history = useHistory();

const onNavigateToEntityRecord = useCallback(
( params ) => {
// Capture current scroll position before navigating
const iframe = document.querySelector(
'iframe[name="editor-canvas"]'
);
const iframeDocument =
iframe?.contentDocument || iframe?.contentWindow?.document;
const scrollElement = iframeDocument?.documentElement;

const currentScrollPosition = scrollElement
? { x: scrollElement.scrollLeft, y: scrollElement.scrollTop }
: { x: window.scrollX, y: window.scrollY };

// Store scroll position for current location
const currentPath =
window.location.pathname + window.location.search;
window.sessionStorage?.setItem(
`gutenberg_scroll_${ currentPath }`,
JSON.stringify( currentScrollPosition )
);

history.navigate(
`/${ params.postType }/${ params.postId }?canvas=edit&focusMode=true`
);
Expand All @@ -25,3 +45,43 @@ export default function useNavigateToEntityRecord() {

return onNavigateToEntityRecord;
}

export function useRestoreScrollPosition() {
const location = useLocation();

useEffect( () => {
// Restore scroll position when location changes
const currentPath = window.location.pathname + window.location.search;
const storedPosition = window.sessionStorage?.getItem(
`gutenberg_scroll_${ currentPath }`
);

if ( storedPosition && typeof window !== 'undefined' ) {
const scrollPosition = JSON.parse( storedPosition );

const restoreScroll = () => {
const iframe = document.querySelector(
'iframe[name="editor-canvas"]'
);
const iframeWindow = iframe?.contentWindow;

if ( iframeWindow ) {
setTimeout( () => {
iframeWindow.scrollTo(
scrollPosition.x,
scrollPosition.y
);
}, 100 );
} else {
window.scrollTo( scrollPosition.x, scrollPosition.y );
}
};

if ( typeof window.requestAnimationFrame !== 'undefined' ) {
window.requestAnimationFrame( restoreScroll );
} else {
restoreScroll();
}
}
}, [ location ] );
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import { store as editorStore } from '@wordpress/editor';
*/
import { store as editSiteStore } from '../../store';
import { unlock } from '../../lock-unlock';
import useNavigateToEntityRecord from './use-navigate-to-entity-record';
import useNavigateToEntityRecord, {
useRestoreScrollPosition,
} from './use-navigate-to-entity-record';
import { FOCUSABLE_ENTITIES } from '../../utils/constants';

const { useLocation, useHistory } = unlock( routerPrivateApis );
Expand All @@ -21,14 +23,45 @@ function useNavigateToPreviousEntityRecord() {
const location = useLocation();
const previousCanvas = usePrevious( location.query.canvas );
const history = useHistory();

const goBack = useMemo( () => {
const isFocusMode =
location.query.focusMode ||
( location?.params?.postId &&
FOCUSABLE_ENTITIES.includes( location?.params?.postType ) );
const didComeFromEditorCanvas = previousCanvas === 'edit';
const showBackButton = isFocusMode && didComeFromEditorCanvas;
return showBackButton ? () => history.back() : undefined;

if ( ! showBackButton ) {
return undefined;
}

return () => {
// Capture current scroll position before navigating back
const iframe = document.querySelector(
'iframe[name="editor-canvas"]'
);
const iframeDocument =
iframe?.contentDocument || iframe?.contentWindow?.document;
const scrollElement = iframeDocument?.documentElement;

const currentScrollPosition = scrollElement
? { x: scrollElement.scrollLeft, y: scrollElement.scrollTop }
: { x: window.scrollX, y: window.scrollY };

// Store scroll position for current location
const currentPath =
window.location.pathname + window.location.search;
window.sessionStorage?.setItem(
`gutenberg_scroll_${ currentPath }`,
JSON.stringify( currentScrollPosition )
);

history.back();
};
// `previousLocation` changes when the component updates for any reason, not
// just when location changes. Until this is fixed we can't add it to deps. See
// https://github.com/WordPress/gutenberg/pull/58710#discussion_r1479219465.
}, [ location, history, previousCanvas ] );
return goBack;
}
Expand All @@ -37,6 +70,10 @@ export function useSpecificEditorSettings() {
const { query } = useLocation();
const { canvas = 'view' } = query;
const onNavigateToEntityRecord = useNavigateToEntityRecord();

// Restore scroll position when navigating
useRestoreScrollPosition();

const { settings, currentPostIsTrashed } = useSelect( ( select ) => {
const { getSettings } = select( editSiteStore );
const { getCurrentPostAttribute } = select( editorStore );
Expand Down