deploy: v0.12 fix 存档key名

This commit is contained in:
2026-06-24 03:09:02 +00:00
parent 7a08d49fe3
commit e0a5281119
58743 changed files with 7297269 additions and 39505 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Jared Lunde
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+92
View File
@@ -0,0 +1,92 @@
<hr>
<div align="center">
<h1 align="center">
useIntersectionObserver()
</h1>
<div align="center">
<a href="https://flexstack.com"><img src="https://flexstack.com/images/supported-by-flexstack.svg" height="38" alt="Supported by FlexStack"></a>
</div>
</div>
<hr>
<p align="center">
<a href="https://bundlephobia.com/result?p=@react-hook/intersection-observer">
<img alt="Bundlephobia" src="https://img.shields.io/bundlephobia/minzip/@react-hook/intersection-observer?style=for-the-badge&labelColor=24292e">
</a>
<a aria-label="Types" href="https://www.npmjs.com/package/@react-hook/intersection-observer">
<img alt="Types" src="https://img.shields.io/npm/types/@react-hook/intersection-observer?style=for-the-badge&labelColor=24292e">
</a>
<a aria-label="Build status" href="https://travis-ci.com/jaredLunde/react-hook">
<img alt="Build status" src="https://img.shields.io/travis/com/jaredLunde/react-hook?style=for-the-badge&labelColor=24292e">
</a>
<a aria-label="NPM version" href="https://www.npmjs.com/package/@react-hook/intersection-observer">
<img alt="NPM Version" src="https://img.shields.io/npm/v/@react-hook/intersection-observer?style=for-the-badge&labelColor=24292e">
</a>
<a aria-label="License" href="https://jaredlunde.mit-license.org/">
<img alt="MIT License" src="https://img.shields.io/npm/l/@react-hook/intersection-observer?style=for-the-badge&labelColor=24292e">
</a>
</p>
<pre align="center">npm i @react-hook/intersection-observer</pre>
A React hook for the IntersectionObserver API that uses a polyfill when the native API is not available
The Intersection Observer API provides a way to asynchronously observe changes in
the intersection of a target element with an ancestor element or with a top-level document's viewport. The ancestor
element or viewport is referred to as the root.
When an IntersectionObserver is created, it's configured to watch for given ratios of visibility within the root. The
configuration cannot be changed once the IntersectionObserver is created, so a given observer object is only useful for
watching for specific changes in degree of visibility.
## Quick Start
```jsx harmony
import * as React from 'react'
import useIntersectionObserver from '@react-hook/intersection-observer'
const Component = () => {
const [ref, setRef] = React.useState()
const {isIntersecting} = useIntersectionObserver(ref)
return <div ref={setRef}>Is intersecting? {isIntersecting}</div>
}
```
## API
### `useIntersectionObserver(target, options)`
```ts
function useIntersectionObserver<T extends HTMLElement = HTMLElement>(
target: React.RefObject<T> | T | null,
options: IntersectionObserverOptions = {}
): IntersectionObserverEntry
```
#### Arguments
| Argument | Type | Required? | Description |
| -------- | ------------------------------------------------------------- | --------- | ---------------------------------------------------- |
| target | <code>React.RefObject<T> &#124; T &#124; null</code> | Yes | A React ref created by `useRef()` or an HTML element |
| options | [`IntersectionObserverOptions`](#intersectionobserveroptions) | No | Configuration options for the IntersectionObserver |
#### IntersectionObserverOptions
| Property | Type | Default | Description |
| --------------------- | --------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| root | `DOMElement` | `document` | A specific ancestor of the target element being observed. If no value was passed to the constructor or this is `null`, the top-level document's viewport is used |
| rootMargin | `string` | `"0 0 0 0"` | Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). The values can be percentages. This set of values serves to grow or shrink each side of the root element's bounding box before computing intersections. |
| threshold | <code>number&#124;number[]</code> | `0` | Either a single number or an array of numbers which indicate at what percentage of the target's visibility the observer's callback should be executed. If you only want to detect when visibility passes the 50% mark, you can use a value of `0.5`. If you want the callback to run every time visibility passes another 25%, you would specify the array `[0, 0.25, 0.5, 0.75, 1]`. The default is 0 (meaning as soon as even one pixel is visible, the callback will be run). A value of 1.0 means that the threshold isn't considered passed until every pixel is visible. |
| pollInterval | `number` | `null` | The frequency in which the polyfill polls for intersection changes. |
| useMutationObserver | `bool` | `true` | You can also choose to not check for intersections in the polyfill when the DOM changes by setting this to `false` |
| initialIsIntersecting | `bool` | `false` | Changes the default value of `isIntersecting` for use in places like SSR |
#### Returns `IntersectionObserverEntry`
| Type | Description |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `IntersectionObserverEntry` | This is the [IntersectionObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) object returned by the `IntersectionObserver` callback. |
## LICENSE
MIT
+144
View File
@@ -0,0 +1,144 @@
import 'intersection-observer';
import { useState, useEffect } from 'react';
import useLayoutEffect from '@react-hook/passive-layout-effect';
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
function useIntersectionObserver(target, options) {
if (options === void 0) {
options = {};
}
var {
root = null,
pollInterval = null,
useMutationObserver = false,
rootMargin = '0px 0px 0px 0px',
threshold = 0,
initialIsIntersecting = false
} = options;
var [entry, setEntry] = useState(() => ({
boundingClientRect: null,
intersectionRatio: 0,
intersectionRect: null,
isIntersecting: initialIsIntersecting,
rootBounds: null,
target: null,
time: 0
}));
var [observer, setObserver] = useState(() => getIntersectionObserver({
root,
pollInterval,
useMutationObserver,
rootMargin,
threshold
}));
useEffect(() => {
var observer = getIntersectionObserver({
root,
pollInterval,
useMutationObserver,
rootMargin,
threshold
});
setObserver(observer); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [root, rootMargin, pollInterval, useMutationObserver, JSON.stringify(threshold)]);
useLayoutEffect(() => {
var targetEl = target && 'current' in target ? target.current : target;
if (!observer || !targetEl) return;
var didUnsubscribe = false;
observer.observer.observe(targetEl);
var callback = entries => {
if (didUnsubscribe) return;
for (var i = 0; i < entries.length; i++) {
var _entry = entries[i];
if (_entry.target === targetEl) {
setEntry(_entry);
}
}
};
observer.subscribe(callback);
return () => {
didUnsubscribe = true;
observer.observer.unobserve(targetEl);
observer.unsubscribe(callback);
};
}, [target, observer]);
return entry;
}
function createIntersectionObserver(_ref) {
var {
root = null,
pollInterval = null,
useMutationObserver = false,
rootMargin = '0px 0px 0px 0px',
threshold = 0
} = _ref;
var callbacks = new Set();
if (typeof IntersectionObserver === 'undefined') return null;
var observer = new IntersectionObserver(entries => {
for (var callback of callbacks) {
callback(entries, observer);
}
}, {
root,
rootMargin,
threshold
}); // @ts-ignore
observer.POLL_INTERVAL = pollInterval; // @ts-ignore
observer.USE_MUTATION_OBSERVER = useMutationObserver;
return {
observer,
getListeners() {
return callbacks;
},
subscribe: callback => callbacks.add(callback),
unsubscribe: callback => callbacks.delete(callback)
};
}
var _intersectionObserver = /*#__PURE__*/new Map();
function getIntersectionObserver(options) {
var {
root
} = options,
keys = _objectWithoutPropertiesLoose(options, ["root"]);
var key = JSON.stringify(keys);
var base = _intersectionObserver.get(root);
if (!base) {
base = {};
_intersectionObserver.set(root, base);
}
return !base[key] ? base[key] = createIntersectionObserver(options) : base[key];
}
export default useIntersectionObserver;
//# sourceMappingURL=index.dev.mjs.map
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
function r(r){var{root:e}=r,t=function(r,e){if(null==r)return{};var t,n,o={},i=Object.keys(r);for(n=0;n<i.length;n++)t=i[n],0>e.indexOf(t)&&(o[t]=r[t]);return o}(r,["root"]),n=JSON.stringify(t),i=o.get(e);return i||(i={},o.set(e,i)),i[n]?i[n]:i[n]=function(r){var{root:e=null,pollInterval:t=null,useMutationObserver:n=0,rootMargin:o="0px 0px 0px 0px",threshold:i=0}=r,s=new Set;if("undefined"==typeof IntersectionObserver)return null;var l=new IntersectionObserver(r=>{for(var e of s)e(r,l)},{root:e,rootMargin:o,threshold:i});return l.POLL_INTERVAL=t,l.USE_MUTATION_OBSERVER=n,{observer:l,getListeners:()=>s,subscribe:r=>s.add(r),unsubscribe:r=>s.delete(r)}}(r)}import"intersection-observer";import{useState as e,useEffect as t}from"react";import n from"@react-hook/passive-layout-effect";var o=new Map;export default function(o,i){void 0===i&&(i={});var{root:s=null,pollInterval:l=null,useMutationObserver:u=0,rootMargin:a="0px 0px 0px 0px",threshold:v=0,initialIsIntersecting:c=0}=i,[b,f]=e(()=>({boundingClientRect:null,intersectionRatio:0,intersectionRect:null,isIntersecting:c,rootBounds:null,target:null,time:0})),[p,g]=e(()=>r({root:s,pollInterval:l,useMutationObserver:u,rootMargin:a,threshold:v}));return t(()=>{var e=r({root:s,pollInterval:l,useMutationObserver:u,rootMargin:a,threshold:v});g(e)},[s,a,l,u,JSON.stringify(v)]),n(()=>{var r=o&&"current"in o?o.current:o;if(p&&r){var e=0;p.observer.observe(r);var t=t=>{if(!e)for(var n=0;n<t.length;n++){var o=t[n];o.target===r&&f(o)}};return p.subscribe(t),()=>{e=1,p.observer.unobserve(r),p.unsubscribe(t)}}},[o,p]),b}
//# sourceMappingURL=index.mjs.map
File diff suppressed because one or more lines are too long
+134
View File
@@ -0,0 +1,134 @@
"use strict";
exports.__esModule = true;
exports.default = void 0;
require("intersection-observer");
var React = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("react"));
var _passiveLayoutEffect = /*#__PURE__*/_interopRequireDefault( /*#__PURE__*/require("@react-hook/passive-layout-effect"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function useIntersectionObserver(target, options = {}) {
const {
root = null,
pollInterval = null,
useMutationObserver = false,
rootMargin = '0px 0px 0px 0px',
threshold = 0,
initialIsIntersecting = false
} = options;
const [entry, setEntry] = React.useState(() => ({
boundingClientRect: null,
intersectionRatio: 0,
intersectionRect: null,
isIntersecting: initialIsIntersecting,
rootBounds: null,
target: null,
time: 0
}));
const [observer, setObserver] = React.useState(() => getIntersectionObserver({
root,
pollInterval,
useMutationObserver,
rootMargin,
threshold
}));
React.useEffect(() => {
const observer = getIntersectionObserver({
root,
pollInterval,
useMutationObserver,
rootMargin,
threshold
});
setObserver(observer); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [root, rootMargin, pollInterval, useMutationObserver, JSON.stringify(threshold)]);
(0, _passiveLayoutEffect.default)(() => {
const targetEl = target && 'current' in target ? target.current : target;
if (!observer || !targetEl) return;
let didUnsubscribe = false;
observer.observer.observe(targetEl);
const callback = entries => {
if (didUnsubscribe) return;
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
if (entry.target === targetEl) {
setEntry(entry);
}
}
};
observer.subscribe(callback);
return () => {
didUnsubscribe = true;
observer.observer.unobserve(targetEl);
observer.unsubscribe(callback);
};
}, [target, observer]);
return entry;
}
function createIntersectionObserver({
root = null,
pollInterval = null,
useMutationObserver = false,
rootMargin = '0px 0px 0px 0px',
threshold = 0
}) {
const callbacks = new Set();
if (typeof IntersectionObserver === 'undefined') return null;
const observer = new IntersectionObserver(entries => {
for (const callback of callbacks) callback(entries, observer);
}, {
root,
rootMargin,
threshold
}); // @ts-ignore
observer.POLL_INTERVAL = pollInterval; // @ts-ignore
observer.USE_MUTATION_OBSERVER = useMutationObserver;
return {
observer,
getListeners() {
return callbacks;
},
subscribe: callback => callbacks.add(callback),
unsubscribe: callback => callbacks.delete(callback)
};
}
const _intersectionObserver = /*#__PURE__*/new Map();
function getIntersectionObserver(options) {
const {
root,
...keys
} = options;
const key = JSON.stringify(keys);
let base = _intersectionObserver.get(root);
if (!base) {
base = {};
_intersectionObserver.set(root, base);
}
return !base[key] ? base[key] = createIntersectionObserver(options) : base[key];
}
var _default = useIntersectionObserver;
exports.default = _default;
+120
View File
@@ -0,0 +1,120 @@
import 'intersection-observer';
import * as React from 'react';
import useLayoutEffect from '@react-hook/passive-layout-effect';
function useIntersectionObserver(target, options = {}) {
const {
root = null,
pollInterval = null,
useMutationObserver = false,
rootMargin = '0px 0px 0px 0px',
threshold = 0,
initialIsIntersecting = false
} = options;
const [entry, setEntry] = React.useState(() => ({
boundingClientRect: null,
intersectionRatio: 0,
intersectionRect: null,
isIntersecting: initialIsIntersecting,
rootBounds: null,
target: null,
time: 0
}));
const [observer, setObserver] = React.useState(() => getIntersectionObserver({
root,
pollInterval,
useMutationObserver,
rootMargin,
threshold
}));
React.useEffect(() => {
const observer = getIntersectionObserver({
root,
pollInterval,
useMutationObserver,
rootMargin,
threshold
});
setObserver(observer); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [root, rootMargin, pollInterval, useMutationObserver, JSON.stringify(threshold)]);
useLayoutEffect(() => {
const targetEl = target && 'current' in target ? target.current : target;
if (!observer || !targetEl) return;
let didUnsubscribe = false;
observer.observer.observe(targetEl);
const callback = entries => {
if (didUnsubscribe) return;
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
if (entry.target === targetEl) {
setEntry(entry);
}
}
};
observer.subscribe(callback);
return () => {
didUnsubscribe = true;
observer.observer.unobserve(targetEl);
observer.unsubscribe(callback);
};
}, [target, observer]);
return entry;
}
function createIntersectionObserver({
root = null,
pollInterval = null,
useMutationObserver = false,
rootMargin = '0px 0px 0px 0px',
threshold = 0
}) {
const callbacks = new Set();
if (typeof IntersectionObserver === 'undefined') return null;
const observer = new IntersectionObserver(entries => {
for (const callback of callbacks) callback(entries, observer);
}, {
root,
rootMargin,
threshold
}); // @ts-ignore
observer.POLL_INTERVAL = pollInterval; // @ts-ignore
observer.USE_MUTATION_OBSERVER = useMutationObserver;
return {
observer,
getListeners() {
return callbacks;
},
subscribe: callback => callbacks.add(callback),
unsubscribe: callback => callbacks.delete(callback)
};
}
const _intersectionObserver = /*#__PURE__*/new Map();
function getIntersectionObserver(options) {
const {
root,
...keys
} = options;
const key = JSON.stringify(keys);
let base = _intersectionObserver.get(root);
if (!base) {
base = {};
_intersectionObserver.set(root, base);
}
return !base[key] ? base[key] = createIntersectionObserver(options) : base[key];
}
export default useIntersectionObserver;
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+135
View File
@@ -0,0 +1,135 @@
{
"name": "@react-hook/intersection-observer",
"version": "3.1.2",
"homepage": "https://github.com/jaredLunde/react-hook/tree/master/packages/intersection-observer#readme",
"repository": "github:jaredLunde/react-hook",
"bugs": "https://github.com/jaredLunde/react-hook/issues",
"author": "Jared Lunde <jared.lunde@gmail.com> (https://jaredLunde.com)",
"license": "MIT",
"description": "A React hook for the IntersectionObserver API that uses a polyfill when the native API is not available",
"keywords": [
"react",
"react hook",
"hook",
"react hooks",
"hooks",
"intersection",
"intersection observer",
"use intersection observer",
"useintersectionobserver"
],
"main": "dist/main/index.js",
"module": "dist/module/index.js",
"unpkg": "dist/umd/use-intersection-observer.js",
"source": "src/index.tsx",
"types": "types/index.d.ts",
"files": [
"/dist",
"/src",
"/types"
],
"exports": {
".": {
"browser": "./dist/module/index.js",
"import": "./dist/esm/index.mjs",
"require": "./dist/main/index.js",
"umd": "./dist/umd/use-intersection-observer.js",
"source": "./src/index.tsx",
"types": "./types/index.d.ts",
"default": "./dist/main/index.js"
},
"./package.json": "./package.json",
"./*": "./*"
},
"sideEffects": false,
"scripts": {
"build": "lundle build --umd-case camel",
"check-types": "lundle check-types",
"dev": "lundle build -f module,cjs -w",
"format": "prettier --write \"{,!(node_modules|dist|coverage)/**/}*.{ts,tsx,js,jsx,md,yml,json}\"",
"lint": "eslint . --ext .ts,.tsx",
"prepublishOnly": "npm run lint && npm run test && npm run build && npm run format",
"test": "jest",
"validate": "lundle check-types && npm run lint && jest --coverage"
},
"husky": {
"hooks": {
"pre-commit": "lundle check-types && lint-staged"
}
},
"lint-staged": {
"**/*.{ts,tsx,js,jsx}": [
"eslint",
"prettier --write"
],
"**/*.{md,yml,json}": [
"prettier --write"
]
},
"eslintConfig": {
"extends": [
"lunde"
]
},
"eslintIgnore": [
"node_modules",
"coverage",
"dist",
"test",
"*.config.js"
],
"jest": {
"moduleDirectories": [
"node_modules",
"src",
"test"
],
"testMatch": [
"<rootDir>/src/**/?(*.)test.{ts,tsx}"
],
"collectCoverageFrom": [
"**/src/**/*.{ts,tsx}"
],
"setupFilesAfterEnv": [
"./test/setup.js"
],
"snapshotResolver": "./test/resolve-snapshot.js",
"globals": {
"__DEV__": true
}
},
"prettier": {
"semi": false,
"singleQuote": true,
"jsxSingleQuote": true,
"bracketSpacing": false
},
"devDependencies": {
"@testing-library/jest-dom": "latest",
"@testing-library/react": "latest",
"@testing-library/react-hooks": "latest",
"@testing-library/user-event": "latest",
"@types/jest": "latest",
"@types/react": "latest",
"@types/react-dom": "latest",
"babel-jest": "latest",
"eslint": "latest",
"eslint-config-lunde": "latest",
"husky": "latest",
"jest": "latest",
"lint-staged": "latest",
"lundle": "^0.4.9",
"prettier": "latest",
"react": "latest",
"react-dom": "latest",
"react-test-renderer": "latest",
"typescript": "latest"
},
"dependencies": {
"@react-hook/passive-layout-effect": "^1.2.0",
"intersection-observer": "^0.10.0"
},
"peerDependencies": {
"react": ">=16.8"
}
}
+93
View File
@@ -0,0 +1,93 @@
import {renderHook, act} from '@testing-library/react-hooks'
import useIntersectionObserver from './index'
const observerMap = new Map()
const instanceMap = new Map()
beforeAll(() => {
// @ts-ignore
global.IntersectionObserver = jest.fn((cb, options) => {
const instance = {
thresholds: Array.isArray(options.threshold)
? options.threshold
: [options.threshold],
root: options.root,
rootMargin: options.rootMargin,
observe: jest.fn((element: Element) => {
instanceMap.set(element, instance)
observerMap.set(element, cb)
}),
unobserve: jest.fn((element: Element) => {
instanceMap.delete(element)
observerMap.delete(element)
}),
disconnect: jest.fn(),
}
return instance
})
})
afterEach(() => {
// @ts-ignore
global.IntersectionObserver.mockClear()
instanceMap.clear()
observerMap.clear()
})
function mockAllIsIntersecting(isIntersecting: boolean): void {
observerMap.forEach((onChange, element) => {
mockIsIntersecting(element, isIntersecting)
})
}
function mockIsIntersecting(element: Element, isIntersecting: boolean): void {
const cb = observerMap.get(element)
if (cb) {
const entry = [
{
isIntersecting,
target: element,
intersectionRatio: isIntersecting ? 1 : 0,
},
]
act(() => cb(entry))
}
}
function intersectionMockInstance(element: Element): IntersectionObserver {
return instanceMap.get(element)
}
it('sets initialIsIntersecting', () => {
expect(
renderHook(() =>
useIntersectionObserver(null, {initialIsIntersecting: true})
).result.current.isIntersecting
).toBe(true)
expect(
renderHook(() =>
useIntersectionObserver(null, {initialIsIntersecting: false})
).result.current.isIntersecting
).toBe(false)
})
it('observes', () => {
const element = document.createElement('div')
renderHook(() => useIntersectionObserver(element))
const instance = intersectionMockInstance(element)
expect(instance.observe).toHaveBeenCalledWith(element)
})
it('should be intersecting', () => {
const element = document.createElement('div')
const {result} = renderHook(() => useIntersectionObserver(element))
expect(result.current.isIntersecting).toBe(false)
mockAllIsIntersecting(true)
expect(result.current.isIntersecting).toBe(true)
mockAllIsIntersecting(false)
expect(result.current.isIntersecting).toBe(false)
})
+168
View File
@@ -0,0 +1,168 @@
import 'intersection-observer'
import * as React from 'react'
import useLayoutEffect from '@react-hook/passive-layout-effect'
function useIntersectionObserver<T extends HTMLElement = HTMLElement>(
target: React.RefObject<T> | T | null,
options: IntersectionObserverOptions = {}
): MockIntersectionObserverEntry | IntersectionObserverEntry {
const {
root = null,
pollInterval = null,
useMutationObserver = false,
rootMargin = '0px 0px 0px 0px',
threshold = 0,
initialIsIntersecting = false,
} = options
const [entry, setEntry] = React.useState<
IntersectionObserverEntry | MockIntersectionObserverEntry
>(() => ({
boundingClientRect: null,
intersectionRatio: 0,
intersectionRect: null,
isIntersecting: initialIsIntersecting,
rootBounds: null,
target: null,
time: 0,
}))
const [observer, setObserver] = React.useState(() =>
getIntersectionObserver({
root,
pollInterval,
useMutationObserver,
rootMargin,
threshold,
})
)
React.useEffect(() => {
const observer = getIntersectionObserver({
root,
pollInterval,
useMutationObserver,
rootMargin,
threshold,
})
setObserver(observer)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
root,
rootMargin,
pollInterval,
useMutationObserver,
JSON.stringify(threshold),
])
useLayoutEffect(() => {
const targetEl = target && 'current' in target ? target.current : target
if (!observer || !targetEl) return
let didUnsubscribe = false
observer.observer.observe(targetEl)
const callback = (entries: IntersectionObserverEntry[]) => {
if (didUnsubscribe) return
for (let i = 0; i < entries.length; i++) {
const entry = entries[i]
if (entry.target === targetEl) {
setEntry(entry)
}
}
}
observer.subscribe(callback)
return () => {
didUnsubscribe = true
observer.observer.unobserve(targetEl)
observer.unsubscribe(callback)
}
}, [target, observer])
return entry
}
function createIntersectionObserver({
root = null,
pollInterval = null,
useMutationObserver = false,
rootMargin = '0px 0px 0px 0px',
threshold = 0,
}: IntersectionObserverOptions) {
const callbacks: Set<IntersectionObserverCallback> = new Set()
if (typeof IntersectionObserver === 'undefined') return null
const observer = new IntersectionObserver(
(entries) => {
for (const callback of callbacks) callback(entries, observer)
},
{root, rootMargin, threshold}
)
// @ts-ignore
observer.POLL_INTERVAL = pollInterval
// @ts-ignore
observer.USE_MUTATION_OBSERVER = useMutationObserver
return {
observer,
getListeners() {
return callbacks
},
subscribe: (callback: IntersectionObserverCallback) =>
callbacks.add(callback),
unsubscribe: (callback: IntersectionObserverCallback) =>
callbacks.delete(callback),
}
}
const _intersectionObserver: Map<
HTMLElement | null | undefined,
Record<string, ReturnType<typeof createIntersectionObserver>>
> = new Map()
function getIntersectionObserver(options: IntersectionObserverOptions) {
const {root, ...keys} = options
const key = JSON.stringify(keys)
let base = _intersectionObserver.get(root)
if (!base) {
base = {}
_intersectionObserver.set(root, base)
}
return !base[key]
? (base[key] = createIntersectionObserver(options))
: base[key]
}
export type UseIntersectionObserverCallback = (
entry: IntersectionObserverEntry,
observer: IntersectionObserver
) => any
export interface IntersectionObserverOptions {
root?: HTMLElement | null
pollInterval?: number | null
useMutationObserver?: boolean
rootMargin?: string
threshold?: number | number[]
initialIsIntersecting?: boolean
}
export interface IntersectionObserverBounds {
readonly height: number
readonly width: number
readonly top: number
readonly left: number
readonly right: number
readonly bottom: number
}
export interface MockIntersectionObserverEntry {
readonly time: number | null
readonly rootBounds: IntersectionObserverBounds | null
readonly boundingClientRect: IntersectionObserverBounds | null
readonly intersectionRect: IntersectionObserverBounds | null
readonly intersectionRatio: number | null
readonly target: HTMLElement | null
readonly isIntersecting: boolean
}
export default useIntersectionObserver
+36
View File
@@ -0,0 +1,36 @@
import 'intersection-observer'
import * as React from 'react'
declare function useIntersectionObserver<T extends HTMLElement = HTMLElement>(
target: React.RefObject<T> | T | null,
options?: IntersectionObserverOptions
): MockIntersectionObserverEntry | IntersectionObserverEntry
export declare type UseIntersectionObserverCallback = (
entry: IntersectionObserverEntry,
observer: IntersectionObserver
) => any
export interface IntersectionObserverOptions {
root?: HTMLElement | null
pollInterval?: number | null
useMutationObserver?: boolean
rootMargin?: string
threshold?: number | number[]
initialIsIntersecting?: boolean
}
export interface IntersectionObserverBounds {
readonly height: number
readonly width: number
readonly top: number
readonly left: number
readonly right: number
readonly bottom: number
}
export interface MockIntersectionObserverEntry {
readonly time: number | null
readonly rootBounds: IntersectionObserverBounds | null
readonly boundingClientRect: IntersectionObserverBounds | null
readonly intersectionRect: IntersectionObserverBounds | null
readonly intersectionRatio: number | null
readonly target: HTMLElement | null
readonly isIntersecting: boolean
}
export default useIntersectionObserver