mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-28 09:47:48 +02:00
fix(ideal-image): Internalize react-waypoint
dependency, fix React 19 compatibility (#11014)
* copy waypoint, remove logs * remove propTypes * remove debug * remove scrollableAncestor prop * remove onPositionChange * remove horizontal prop * remove fireOnRapidScroll * remove useless render code * remove ensureRefIsUsedByChild * remove children prop * inline constants * remove consolidated-events * copy getCurrentPosition * remove computeOffsetPixels * extract findScrollableAncestor * extract getBounds * remove hasWindow * remove onNextTick() * fixes * make it work, replace waypoint * slim down * slim down * slim down * use TypeScript * slim down * slim down * revert
This commit is contained in:
parent
fcee060f40
commit
43fdb825e8
4 changed files with 242 additions and 20 deletions
|
@ -26,7 +26,6 @@
|
|||
"@docusaurus/theme-translations": "3.7.0",
|
||||
"@docusaurus/types": "3.7.0",
|
||||
"@docusaurus/utils-validation": "3.7.0",
|
||||
"react-waypoint": "^10.3.0",
|
||||
"sharp": "^0.32.3",
|
||||
"tslib": "^2.6.0",
|
||||
"webpack": "^5.88.1"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, {Component} from 'react';
|
||||
// import PropTypes from 'prop-types'
|
||||
import {Waypoint} from 'react-waypoint';
|
||||
import {Waypoint} from './waypoint';
|
||||
import Media from '../Media';
|
||||
import {icons, loadStates} from '../constants';
|
||||
import {xhrLoader, imageLoader, timeout, combineCancel} from '../loaders';
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
/*
|
||||
This is a slimmed down copy of https://github.com/civiccc/react-waypoint
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2015 Brigade
|
||||
*/
|
||||
|
||||
import React, {createRef, ReactNode} from 'react';
|
||||
|
||||
type ScrollContainer = Window | HTMLElement;
|
||||
|
||||
function addEventListener(
|
||||
element: ScrollContainer,
|
||||
type: 'scroll' | 'resize',
|
||||
listener: () => void,
|
||||
options: AddEventListenerOptions,
|
||||
) {
|
||||
element.addEventListener(type, listener, options);
|
||||
return () => element.removeEventListener(type, listener, options);
|
||||
}
|
||||
|
||||
type Position = 'above' | 'inside' | 'below' | 'invisible';
|
||||
|
||||
type Props = {
|
||||
topOffset: number;
|
||||
bottomOffset: number;
|
||||
onEnter: () => void;
|
||||
onLeave: () => void;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export function Waypoint(props: Props) {
|
||||
return typeof window !== 'undefined' ? (
|
||||
<WaypointClient {...props}>{props.children}</WaypointClient>
|
||||
) : (
|
||||
props.children
|
||||
);
|
||||
}
|
||||
|
||||
// TODO maybe replace this with IntersectionObserver later?
|
||||
// IntersectionObserver doesn't support the "fast scroll" thing
|
||||
// but it's probably not a big deal
|
||||
class WaypointClient extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
topOffset: 0,
|
||||
bottomOffset: 0,
|
||||
onEnter() {},
|
||||
onLeave() {},
|
||||
};
|
||||
|
||||
scrollableAncestor?: ScrollContainer;
|
||||
previousPosition: Position | null = null;
|
||||
unsubscribe?: () => void;
|
||||
|
||||
innerRef = createRef<HTMLElement>();
|
||||
|
||||
override componentDidMount() {
|
||||
this.scrollableAncestor = findScrollableAncestor(this.innerRef.current!);
|
||||
|
||||
const unsubscribeScroll = addEventListener(
|
||||
this.scrollableAncestor!,
|
||||
'scroll',
|
||||
this._handleScroll,
|
||||
{passive: true},
|
||||
);
|
||||
|
||||
const unsubscribeResize = addEventListener(
|
||||
window,
|
||||
'resize',
|
||||
this._handleScroll,
|
||||
{passive: true},
|
||||
);
|
||||
|
||||
this.unsubscribe = () => {
|
||||
unsubscribeScroll();
|
||||
unsubscribeResize();
|
||||
};
|
||||
|
||||
this._handleScroll();
|
||||
}
|
||||
|
||||
override componentDidUpdate() {
|
||||
this._handleScroll();
|
||||
}
|
||||
|
||||
override componentWillUnmount() {
|
||||
this.unsubscribe?.();
|
||||
}
|
||||
|
||||
_handleScroll = () => {
|
||||
const node = this.innerRef.current;
|
||||
const {topOffset, bottomOffset, onEnter, onLeave} = this.props;
|
||||
|
||||
const bounds = getBounds({
|
||||
node: node!,
|
||||
scrollableAncestor: this.scrollableAncestor!,
|
||||
topOffset,
|
||||
bottomOffset,
|
||||
});
|
||||
|
||||
const currentPosition = getCurrentPosition(bounds);
|
||||
const previousPosition = this.previousPosition;
|
||||
this.previousPosition = currentPosition;
|
||||
|
||||
if (previousPosition === currentPosition) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPosition === 'inside') {
|
||||
onEnter();
|
||||
} else if (previousPosition === 'inside') {
|
||||
onLeave();
|
||||
}
|
||||
|
||||
const isRapidScrollDown =
|
||||
previousPosition === 'below' && currentPosition === 'above';
|
||||
const isRapidScrollUp =
|
||||
previousPosition === 'above' && currentPosition === 'below';
|
||||
if (isRapidScrollDown || isRapidScrollUp) {
|
||||
onEnter();
|
||||
onLeave();
|
||||
}
|
||||
};
|
||||
|
||||
override render() {
|
||||
// @ts-expect-error: fix this implicit API
|
||||
return React.cloneElement(this.props.children, {innerRef: this.innerRef});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses up the DOM to find an ancestor container which has an overflow
|
||||
* style that allows for scrolling.
|
||||
*
|
||||
* @return {Object} the closest ancestor element with an overflow style that
|
||||
* allows for scrolling. If none is found, the `window` object is returned
|
||||
* as a fallback.
|
||||
*/
|
||||
function findScrollableAncestor(inputNode: HTMLElement): ScrollContainer {
|
||||
let node: HTMLElement = inputNode;
|
||||
|
||||
while (node.parentNode) {
|
||||
// @ts-expect-error: it's fine
|
||||
node = node.parentNode!;
|
||||
|
||||
if (node === document.body) {
|
||||
// We've reached all the way to the root node.
|
||||
return window;
|
||||
}
|
||||
|
||||
const style = window.getComputedStyle(node);
|
||||
const overflow =
|
||||
style.getPropertyValue('overflow-y') ||
|
||||
style.getPropertyValue('overflow');
|
||||
|
||||
if (
|
||||
overflow === 'auto' ||
|
||||
overflow === 'scroll' ||
|
||||
overflow === 'overlay'
|
||||
) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
// A scrollable ancestor element was not found, which means that we need to
|
||||
// do stuff on window.
|
||||
return window;
|
||||
}
|
||||
|
||||
type Bounds = {
|
||||
top: number;
|
||||
bottom: number;
|
||||
viewportTop: number;
|
||||
viewportBottom: number;
|
||||
};
|
||||
|
||||
function getBounds({
|
||||
node,
|
||||
scrollableAncestor,
|
||||
topOffset,
|
||||
bottomOffset,
|
||||
}: {
|
||||
node: Element;
|
||||
scrollableAncestor: ScrollContainer;
|
||||
topOffset: number;
|
||||
bottomOffset: number;
|
||||
}): Bounds {
|
||||
const {top, bottom} = node.getBoundingClientRect();
|
||||
|
||||
let contextHeight;
|
||||
let contextScrollTop;
|
||||
if (scrollableAncestor === window) {
|
||||
contextHeight = window.innerHeight;
|
||||
contextScrollTop = 0;
|
||||
} else {
|
||||
const ancestorElement = scrollableAncestor as HTMLElement;
|
||||
contextHeight = ancestorElement.offsetHeight;
|
||||
contextScrollTop = ancestorElement.getBoundingClientRect().top;
|
||||
}
|
||||
|
||||
const contextBottom = contextScrollTop + contextHeight;
|
||||
|
||||
return {
|
||||
top,
|
||||
bottom,
|
||||
viewportTop: contextScrollTop + topOffset,
|
||||
viewportBottom: contextBottom - bottomOffset,
|
||||
};
|
||||
}
|
||||
|
||||
function getCurrentPosition(bounds: Bounds): Position {
|
||||
if (bounds.viewportBottom - bounds.viewportTop === 0) {
|
||||
return 'invisible';
|
||||
}
|
||||
// top is within the viewport
|
||||
if (bounds.viewportTop <= bounds.top && bounds.top <= bounds.viewportBottom) {
|
||||
return 'inside';
|
||||
}
|
||||
// bottom is within the viewport
|
||||
if (
|
||||
bounds.viewportTop <= bounds.bottom &&
|
||||
bounds.bottom <= bounds.viewportBottom
|
||||
) {
|
||||
return 'inside';
|
||||
}
|
||||
// top is above the viewport and bottom is below the viewport
|
||||
if (
|
||||
bounds.top <= bounds.viewportTop &&
|
||||
bounds.viewportBottom <= bounds.bottom
|
||||
) {
|
||||
return 'inside';
|
||||
}
|
||||
if (bounds.viewportBottom < bounds.top) {
|
||||
return 'below';
|
||||
}
|
||||
if (bounds.top < bounds.viewportTop) {
|
||||
return 'above';
|
||||
}
|
||||
return 'invisible';
|
||||
}
|
19
yarn.lock
19
yarn.lock
|
@ -6249,11 +6249,6 @@ console-control-strings@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
|
||||
integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
|
||||
|
||||
"consolidated-events@^1.1.0 || ^2.0.0":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/consolidated-events/-/consolidated-events-2.0.2.tgz#da8d8f8c2b232831413d9e190dc11669c79f4a91"
|
||||
integrity sha512-2/uRVMdRypf5z/TW/ncD/66l75P5hH2vM/GR8Jf8HLc2xnfJtmina6F6du8+v4Z2vTrMo7jC+W1tmEEuuELgkQ==
|
||||
|
||||
content-disposition@0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
|
||||
|
@ -14841,7 +14836,7 @@ promzard@^0.3.0:
|
|||
dependencies:
|
||||
read "1"
|
||||
|
||||
prop-types@^15.0.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
|
@ -15052,7 +15047,7 @@ react-fast-compare@^3.2.0:
|
|||
react-fast-compare "^3.2.0"
|
||||
shallowequal "^1.1.0"
|
||||
|
||||
"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", "react-is@^17.0.1 || ^18.0.0", react-is@^18.0.0, react-is@^18.3.1:
|
||||
"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.3.1:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
|
||||
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
|
||||
|
@ -15152,16 +15147,6 @@ react-test-renderer@^18.0.0:
|
|||
react-shallow-renderer "^16.15.0"
|
||||
scheduler "^0.23.2"
|
||||
|
||||
react-waypoint@^10.3.0:
|
||||
version "10.3.0"
|
||||
resolved "https://registry.yarnpkg.com/react-waypoint/-/react-waypoint-10.3.0.tgz#fcc60e86c6c9ad2174fa58d066dc6ae54e3df71d"
|
||||
integrity sha512-iF1y2c1BsoXuEGz08NoahaLFIGI9gTUAAOKip96HUmylRT6DUtpgoBPjk/Y8dfcFVmfVDvUzWjNXpZyKTOV0SQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
consolidated-events "^1.1.0 || ^2.0.0"
|
||||
prop-types "^15.0.0"
|
||||
react-is "^17.0.1 || ^18.0.0"
|
||||
|
||||
react@16.14.0:
|
||||
version "16.14.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"
|
||||
|
|
Loading…
Add table
Reference in a new issue