mirror of https://github.com/mastodon/mastodon
Replace `react-router-scroll-4` with inlined implementation (#36253)
parent
6d2493ca7c
commit
d801cf8e59
@ -1,18 +0,0 @@
|
||||
import { ScrollContainer as OriginalScrollContainer } from 'react-router-scroll-4';
|
||||
|
||||
// ScrollContainer is used to automatically scroll to the top when pushing a
|
||||
// new history state and remembering the scroll position when going back.
|
||||
// There are a few things we need to do differently, though.
|
||||
const defaultShouldUpdateScroll = (prevRouterProps, { location }) => {
|
||||
// If the change is caused by opening a modal, do not scroll to top
|
||||
return !(location.state?.mastodonModalKey && location.state?.mastodonModalKey !== prevRouterProps?.location?.state?.mastodonModalKey);
|
||||
};
|
||||
|
||||
export default
|
||||
class ScrollContainer extends OriginalScrollContainer {
|
||||
|
||||
static defaultProps = {
|
||||
shouldUpdateScroll: defaultShouldUpdateScroll,
|
||||
};
|
||||
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
import type { MastodonLocation } from 'mastodon/components/router';
|
||||
|
||||
export type ShouldUpdateScrollFn = (
|
||||
prevLocationContext: MastodonLocation | null,
|
||||
locationContext: MastodonLocation,
|
||||
) => boolean;
|
||||
|
||||
/**
|
||||
* ScrollBehavior will automatically scroll to the top on navigations
|
||||
* or restore saved scroll positions, but on some location changes we
|
||||
* need to prevent this.
|
||||
*/
|
||||
|
||||
export const defaultShouldUpdateScroll: ShouldUpdateScrollFn = (
|
||||
prevLocation,
|
||||
location,
|
||||
) => {
|
||||
// If the change is caused by opening a modal, do not scroll to top
|
||||
const shouldUpdateScroll = !(
|
||||
location.state?.mastodonModalKey &&
|
||||
location.state.mastodonModalKey !== prevLocation?.state?.mastodonModalKey
|
||||
);
|
||||
|
||||
return shouldUpdateScroll;
|
||||
};
|
||||
@ -0,0 +1,62 @@
|
||||
import React, { useContext, useEffect, useRef } from 'react';
|
||||
|
||||
import { defaultShouldUpdateScroll } from './default_should_update_scroll';
|
||||
import type { ShouldUpdateScrollFn } from './default_should_update_scroll';
|
||||
import { ScrollBehaviorContext } from './scroll_context';
|
||||
|
||||
interface ScrollContainerProps {
|
||||
/**
|
||||
* This key must be static for the element & not change
|
||||
* while the component is mounted.
|
||||
*/
|
||||
scrollKey: string;
|
||||
shouldUpdateScroll?: ShouldUpdateScrollFn;
|
||||
children: React.ReactElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* `ScrollContainer` is used to manage the scroll position of elements on the page
|
||||
* that can be scrolled independently of the page body.
|
||||
* This component is a port of the unmaintained https://github.com/ytase/react-router-scroll/
|
||||
*/
|
||||
|
||||
export const ScrollContainer: React.FC<ScrollContainerProps> = ({
|
||||
children,
|
||||
scrollKey,
|
||||
shouldUpdateScroll = defaultShouldUpdateScroll,
|
||||
}) => {
|
||||
const scrollBehaviorContext = useContext(ScrollBehaviorContext);
|
||||
|
||||
const containerRef = useRef<HTMLElement>();
|
||||
|
||||
/**
|
||||
* Register/unregister scrollable element with ScrollBehavior
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!scrollBehaviorContext || !containerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollBehaviorContext.registerElement(
|
||||
scrollKey,
|
||||
containerRef.current,
|
||||
(prevLocation, location) => {
|
||||
// Hack to allow accessing scrollBehavior._stateStorage
|
||||
return shouldUpdateScroll.call(
|
||||
scrollBehaviorContext.scrollBehavior,
|
||||
prevLocation,
|
||||
location,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
scrollBehaviorContext.unregisterElement(scrollKey);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return React.Children.only(
|
||||
React.cloneElement(children, { ref: containerRef }),
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,46 @@
|
||||
import type { LocationBase, ScrollPosition } from 'scroll-behavior';
|
||||
|
||||
const STATE_KEY_PREFIX = '@@scroll|';
|
||||
|
||||
interface LocationBaseWithKey extends LocationBase {
|
||||
key?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This module is part of our port of https://github.com/ytase/react-router-scroll/
|
||||
* and handles storing scroll positions in SessionStorage.
|
||||
* Stored positions (`[x, y]`) are keyed by the location key and an optional
|
||||
* `scrollKey` that's used for to track separately scrollable elements other
|
||||
* than the document body.
|
||||
*/
|
||||
|
||||
export class SessionStorage {
|
||||
read(
|
||||
location: LocationBaseWithKey,
|
||||
key: string | null,
|
||||
): ScrollPosition | null {
|
||||
const stateKey = this.getStateKey(location, key);
|
||||
|
||||
try {
|
||||
const value = sessionStorage.getItem(stateKey);
|
||||
return value ? (JSON.parse(value) as ScrollPosition) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
save(location: LocationBaseWithKey, key: string | null, value: unknown) {
|
||||
const stateKey = this.getStateKey(location, key);
|
||||
const storedValue = JSON.stringify(value);
|
||||
|
||||
try {
|
||||
sessionStorage.setItem(stateKey, storedValue);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
getStateKey(location: LocationBaseWithKey, key: string | null) {
|
||||
const locationKey = location.key;
|
||||
const stateKeyBase = `${STATE_KEY_PREFIX}${locationKey}`;
|
||||
return key == null ? stateKeyBase : `${stateKeyBase}|${key}`;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue