mirror of
https://github.com/streamich/react-use.git
synced 2026-01-25 14:17:16 +00:00
Merge https://github.com/streamich/react-use into feat/enhance_useDebounce
This commit is contained in:
commit
036edfdeee
@ -9,10 +9,19 @@ indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
max_line_length = 120
|
||||
|
||||
[*.{ts, tsx}]
|
||||
ij_typescript_enforce_trailing_comma = keep
|
||||
ij_typescript_use_double_quotes = false
|
||||
ij_typescript_force_quote_style = true
|
||||
ij_typescript_align_imports = false
|
||||
ij_typescript_align_multiline_ternary_operation = false
|
||||
ij_typescript_align_multiline_parameters_in_calls = false
|
||||
ij_typescript_align_multiline_parameters = false
|
||||
ij_typescript_align_multiline_chained_methods = false
|
||||
ij_typescript_else_on_new_line = false
|
||||
ij_typescript_catch_on_new_line = false
|
||||
ij_typescript_spaces_within_interpolation_expressions = false
|
||||
|
||||
[*.md]
|
||||
max_line_length = 0
|
||||
|
||||
26
.github/workflows/pull_request_ci.yml
vendored
Normal file
26
.github/workflows/pull_request_ci.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
name: Node CI
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x, 12.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: npm install, build, and test
|
||||
run: |
|
||||
yarn install --pure-lockfile
|
||||
yarn build
|
||||
yarn test
|
||||
env:
|
||||
CI: true
|
||||
93
CHANGELOG.md
93
CHANGELOG.md
@ -1,3 +1,96 @@
|
||||
## [11.3.1](https://github.com/streamich/react-use/compare/v11.3.0...v11.3.1) (2019-08-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update createReducer to fix initial state ([fd083f2](https://github.com/streamich/react-use/commit/fd083f2))
|
||||
|
||||
# [11.3.0](https://github.com/streamich/react-use/compare/v11.2.0...v11.3.0) (2019-08-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add usePreviousDistinct ([#551](https://github.com/streamich/react-use/issues/551)) ([6c3e569](https://github.com/streamich/react-use/commit/6c3e569))
|
||||
|
||||
# [11.2.0](https://github.com/streamich/react-use/compare/v11.1.1...v11.2.0) (2019-08-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add useCircularIterate ([8d84340](https://github.com/streamich/react-use/commit/8d84340))
|
||||
|
||||
## [11.1.1](https://github.com/streamich/react-use/compare/v11.1.0...v11.1.1) (2019-08-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#550](https://github.com/streamich/react-use/issues/550) ([2617d74](https://github.com/streamich/react-use/commit/2617d74))
|
||||
|
||||
# [11.1.0](https://github.com/streamich/react-use/compare/v11.0.2...v11.1.0) (2019-08-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 add useHarmonicIntervalFn() hook ([d9f21e3](https://github.com/streamich/react-use/commit/d9f21e3))
|
||||
|
||||
## [11.0.2](https://github.com/streamich/react-use/compare/v11.0.1...v11.0.2) (2019-08-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **useSetState:** memoize setState callback ([0275329](https://github.com/streamich/react-use/commit/0275329))
|
||||
* **useSetState:** memoize setState callback ([16f023f](https://github.com/streamich/react-use/commit/16f023f))
|
||||
|
||||
## [11.0.1](https://github.com/streamich/react-use/compare/v11.0.0...v11.0.1) (2019-08-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correct useSpring() hook behaviour ([d7a622d](https://github.com/streamich/react-use/commit/d7a622d))
|
||||
|
||||
# [11.0.0](https://github.com/streamich/react-use/compare/v10.8.0...v11.0.0) (2019-08-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add cancel and reset methods to useTimeout ([283045a](https://github.com/streamich/react-use/commit/283045a))
|
||||
* add useTimeoutFn ([284e6fd](https://github.com/streamich/react-use/commit/284e6fd))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* useTimeout now returns a tuple
|
||||
|
||||
# [10.8.0](https://github.com/streamich/react-use/compare/v10.7.1...v10.8.0) (2019-08-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Reworked useBattery hook ([1069060](https://github.com/streamich/react-use/commit/1069060))
|
||||
* succeed useRafLoop tests ([09167df](https://github.com/streamich/react-use/commit/09167df))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 support useBattery hook on server side ([5d31cf0](https://github.com/streamich/react-use/commit/5d31cf0))
|
||||
* 🎸 use only one useState and one useEffect call ([2d0fabf](https://github.com/streamich/react-use/commit/2d0fabf))
|
||||
|
||||
## [10.7.1](https://github.com/streamich/react-use/compare/v10.7.0...v10.7.1) (2019-08-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* async test warnings ([#543](https://github.com/streamich/react-use/issues/543)) ([7af237e](https://github.com/streamich/react-use/commit/7af237e))
|
||||
|
||||
# [10.7.0](https://github.com/streamich/react-use/compare/v10.6.4...v10.7.0) (2019-08-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 add useUpsert ([6875e13](https://github.com/streamich/react-use/commit/6875e13))
|
||||
* 🎸 export useUpsert from index ([3eda2b2](https://github.com/streamich/react-use/commit/3eda2b2))
|
||||
* add useUpsert ([a7c2899](https://github.com/streamich/react-use/commit/a7c2899))
|
||||
|
||||
## [10.6.4](https://github.com/streamich/react-use/compare/v10.6.3...v10.6.4) (2019-08-19)
|
||||
|
||||
|
||||
|
||||
@ -79,9 +79,10 @@
|
||||
<br/>
|
||||
- [**Animations**](./docs/Animations.md)
|
||||
- [`useRaf`](./docs/useRaf.md) — re-renders component on each `requestAnimationFrame`.
|
||||
- [`useInterval`](./docs/useInterval.md) — re-renders component on a set interval using `setInterval`.
|
||||
- [`useInterval`](./docs/useInterval.md) and [`useHarmonicIntervalFn`](./docs/useHarmonicIntervalFn.md) — re-renders component on a set interval using `setInterval`.
|
||||
- [`useSpring`](./docs/useSpring.md) — interpolates number over time according to spring dynamics.
|
||||
- [`useTimeout`](./docs/useTimeout.md) — returns true after a timeout.
|
||||
- [`useTimeout`](./docs/useTimeout.md) — re-renders component after a timeout.
|
||||
- [`useTimeoutFn`](./docs/useTimeoutFn.md) — calls given function after a timeout. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/animation-usetimeoutfn--demo)
|
||||
- [`useTween`](./docs/useTween.md) — re-renders component, while tweening a number from 0 to 1. [![][img-demo]](https://codesandbox.io/s/52990wwzyl)
|
||||
- [`useUpdate`](./docs/useUpdate.md) — returns a callback, which re-renders component when called.
|
||||
<br/>
|
||||
@ -126,6 +127,7 @@
|
||||
- [`usePrevious`](./docs/usePrevious.md) — returns the previous state or props.
|
||||
- [`useObservable`](./docs/useObservable.md) — tracks latest value of an `Observable`.
|
||||
- [`useSetState`](./docs/useSetState.md) — creates `setState` method which works like `this.setState`. [![][img-demo]](https://codesandbox.io/s/n75zqn1xp0)
|
||||
- [`useStateList`](./docs/useStateList.md) — circularly iterates over an array.
|
||||
- [`useToggle` and `useBoolean`](./docs/useToggle.md) — tracks state of a boolean.
|
||||
- [`useCounter` and `useNumber`](./docs/useCounter.md) — tracks state of a number. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usecounter--demo)
|
||||
- [`useList`](./docs/useList.md) — tracks state of an array.
|
||||
|
||||
@ -45,7 +45,7 @@ const Demo = ({ initialCount = 1 }) => {
|
||||
<p>count: {state.count}</p>
|
||||
<button onClick={() => dispatch(addAndReset())}>Add and reset</button>
|
||||
<button
|
||||
onClick={() => dispatch({ type: 'reset', payload: initialCount })}
|
||||
onClick={() => dispatch({ type: 'reset', payload: { count: initialCount }})}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
|
||||
React sensor hook that tracks battery status.
|
||||
|
||||
>**Note:** current `BatteryManager` API state is obsolete.
|
||||
>Although it may still work in some browsers, its use is discouraged since it could be removed at any time.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
@ -9,12 +12,47 @@ React sensor hook that tracks battery status.
|
||||
import {useBattery} from 'react-use';
|
||||
|
||||
const Demo = () => {
|
||||
const state = useBattery();
|
||||
const batteryState = useBattery();
|
||||
|
||||
if (!batteryState.isSupported) {
|
||||
return (
|
||||
<div>
|
||||
<strong>Battery sensor</strong>: <span>not supported</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!batteryState.fetched) {
|
||||
return (
|
||||
<div>
|
||||
<strong>Battery sensor</strong>: <span>supported</span> <br />
|
||||
<strong>Battery state</strong>: <span>fetching</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<pre>
|
||||
{JSON.stringify(state, null, 2)}
|
||||
</pre>
|
||||
<div>
|
||||
<strong>Battery sensor</strong>: <span>supported</span> <br />
|
||||
<strong>Battery state</strong>: <span>fetched</span> <br />
|
||||
<strong>Charge level</strong>: <span>{ (batteryState.level * 100).toFixed(0) }%</span> <br />
|
||||
<strong>Charging</strong>: <span>{ batteryState.charging ? 'yes' : 'no' }</span> <br />
|
||||
<strong>Charging time</strong>:
|
||||
<span>{ batteryState.chargingTime ? batteryState.chargingTime : 'finished' }</span> <br />
|
||||
<strong>Discharging time</strong>: <span>{ batteryState.dischargingTime }</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
```ts
|
||||
const {isSupported, level, charging, dischargingTime, chargingTime} = useBattery();
|
||||
```
|
||||
- **`isSupported`**_`: boolean`_ - whether browser/devise supports BatteryManager;
|
||||
- **`fetched`**_`: boolean`_ - whether battery state is fetched;
|
||||
- **`level`**_`: number`_ - representing the system's battery charge level scaled to a value between 0.0 and 1.0.
|
||||
- **`charging`**_`: boolean`_ - indicating whether or not the battery is currently being charged.
|
||||
- **`dischargingTime`**_`: number`_ - remaining time in seconds until the battery is completely discharged and the system will suspend.
|
||||
- **`chargingTime`**_`: number`_ - remaining time in seconds until the battery is fully charged, or 0 if the battery is already fully charged.
|
||||
|
||||
14
docs/useHarmonicIntervalFn.md
Normal file
14
docs/useHarmonicIntervalFn.md
Normal file
@ -0,0 +1,14 @@
|
||||
# `useHarmonicIntervalFn`
|
||||
|
||||
Same as [`useInterval`](./useInterval.md) hook, but triggers all effects with the same delay
|
||||
at the same time.
|
||||
|
||||
For example, this allows you to create ticking clocks on the page which re-render second counter
|
||||
all at the same time.
|
||||
|
||||
|
||||
## Reference
|
||||
|
||||
```js
|
||||
useHarmonicIntervalFn(fn, delay?: number)
|
||||
```
|
||||
@ -2,7 +2,12 @@
|
||||
|
||||
React side-effect hook that locks scrolling on the body element. Useful for modal and other overlay components.
|
||||
|
||||
## Usage
|
||||
Accepts ref object pointing to any HTML element as second parameter. Parent body element will be found and it's scroll will be locked/unlocked. It is needed to proper iFrame handling.
|
||||
By default it uses body element of script's parent window.
|
||||
|
||||
>Note: To improve performance you can pass body's or iframe's ref object, thus no parent lookup will be performed
|
||||
|
||||
## Usage
|
||||
|
||||
```jsx
|
||||
import {useLockBodyScroll, useToggle} from 'react-use';
|
||||
@ -25,7 +30,8 @@ const Demo = () => {
|
||||
## Reference
|
||||
|
||||
```ts
|
||||
useLockBodyScroll(enabled?: boolean = true);
|
||||
useLockBodyScroll(locked: boolean = true, elementRef?: RefObject<HTMLElement>);
|
||||
```
|
||||
|
||||
- `enabled` — Hook will lock scrolling on the body element if `true`, defaults to `true`
|
||||
- `locked` — Hook will lock scrolling on the body element if `true`, defaults to `true`
|
||||
- `elementRef` — The element ref object to find the body element. Can be either a ref to body or iframe element.
|
||||
|
||||
51
docs/usePreviousDistinct.md
Normal file
51
docs/usePreviousDistinct.md
Normal file
@ -0,0 +1,51 @@
|
||||
# `usePreviousDistinct`
|
||||
|
||||
Just like `usePrevious` but it will only update once the value actually changes. This is important when other
|
||||
hooks are involved and you aren't just interested in the previous props version, but want to know the previous
|
||||
distinct value
|
||||
|
||||
## Usage
|
||||
|
||||
```jsx
|
||||
import {usePreviousDistinct, useCounter} from 'react-use';
|
||||
|
||||
const Demo = () => {
|
||||
const [count, { inc: relatedInc }] = useCounter(0);
|
||||
const [unrelatedCount, { inc }] = useCounter(0);
|
||||
const prevCount = usePreviousDistinct(count);
|
||||
|
||||
return (
|
||||
<p>
|
||||
Now: {count}, before: {prevCount}
|
||||
<button onClick={() => relatedInc()}>Increment</button>
|
||||
Unrelated: {unrelatedCount}
|
||||
<button onClick={() => inc()}>Increment Unrelated</button>
|
||||
</p>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
You can also provide a way of identifying the value as unique. By default, a strict equals is used.
|
||||
|
||||
```jsx
|
||||
import {usePreviousDistinct} from 'react-use';
|
||||
|
||||
const Demo = () => {
|
||||
const [str, setStr] = React.useState("something_lowercase");
|
||||
const [unrelatedCount] = React.useState(0);
|
||||
const prevStr = usePreviousDistinct(str, (prev, next) => (prev && prev.toUpperCase()) === next.toUpperCase());
|
||||
|
||||
return (
|
||||
<p>
|
||||
Now: {count}, before: {prevCount}
|
||||
Unrelated: {unrelatedCount}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
```ts
|
||||
const prevState = usePreviousDistinct = <T>(state: T, compare?: (prev: T | undefined, next: T) => boolean): T;
|
||||
```
|
||||
@ -1,5 +1,8 @@
|
||||
# `useRefMounted`
|
||||
|
||||
>**DEPRECATED**
|
||||
>This method is obsolete, use `useMountedState` instead.
|
||||
|
||||
Lifecycle hook that tracks if component is mounted. Returns a ref, which has a
|
||||
boolean `.current` property.
|
||||
|
||||
|
||||
25
docs/useStateList.md
Normal file
25
docs/useStateList.md
Normal file
@ -0,0 +1,25 @@
|
||||
# `useStateList`
|
||||
|
||||
React state hook that circularly iterates over an array.
|
||||
|
||||
## Usage
|
||||
|
||||
```jsx
|
||||
import { useStateList } from 'react-use';
|
||||
|
||||
const stateSet = ['first', 'second', 'third', 'fourth', 'fifth'];
|
||||
|
||||
const Demo = () => {
|
||||
const {state, prev, next} = useStateList(stateSet);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<pre>{state}</pre>
|
||||
<button onClick={() => prev()}>prev</button>
|
||||
<button onClick={() => next()}>next</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
> If the `stateSet` is changed by a shorter one the hook will select the last element of it.
|
||||
@ -1,15 +1,48 @@
|
||||
# `useTimeout`
|
||||
|
||||
Returns `true` after a specified number of milliseconds.
|
||||
Re-renders the component after a specified number of milliseconds.
|
||||
Provides handles to cancel and/or reset the timeout.
|
||||
|
||||
## Usage
|
||||
|
||||
```jsx
|
||||
import { useTimeout } from 'react-use';
|
||||
|
||||
const Demo = () => {
|
||||
const ready = useTimeout(2000);
|
||||
function TestComponent(props: { ms?: number } = {}) {
|
||||
const ms = props.ms || 5000;
|
||||
const [isReady, cancel] = useTimeout(ms);
|
||||
|
||||
return <div>Ready: {ready ? 'Yes' : 'No'}</div>;
|
||||
return (
|
||||
<div>
|
||||
{ isReady() ? 'I\'m reloaded after timeout' : `I will be reloaded after ${ ms / 1000 }s` }
|
||||
{ isReady() === false ? <button onClick={ cancel }>Cancel</button> : '' }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Demo = () => {
|
||||
return (
|
||||
<div>
|
||||
<TestComponent />
|
||||
<TestComponent ms={ 10000 } />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
```ts
|
||||
const [
|
||||
isReady: () => boolean | null,
|
||||
cancel: () => void,
|
||||
reset: () => void,
|
||||
] = useTimeout(ms: number = 0);
|
||||
```
|
||||
|
||||
- **`isReady`**_` :()=>boolean|null`_ - function returning current timeout state:
|
||||
- `false` - pending re-render
|
||||
- `true` - re-render performed
|
||||
- `null` - re-render cancelled
|
||||
- **`cancel`**_` :()=>void`_ - cancel the timeout (component will not be re-rendered)
|
||||
- **`reset`**_` :()=>void`_ - reset the timeout
|
||||
|
||||
65
docs/useTimeoutFn.md
Normal file
65
docs/useTimeoutFn.md
Normal file
@ -0,0 +1,65 @@
|
||||
# `useTimeoutFn`
|
||||
|
||||
Calls given function after specified amount of milliseconds.
|
||||
**Note:** this hook does not re-render component by itself.
|
||||
|
||||
Automatically cancels timeout on component unmount.
|
||||
Automatically resets timeout on delay change.
|
||||
|
||||
## Usage
|
||||
|
||||
```jsx
|
||||
import * as React from 'react';
|
||||
import { useTimeoutFn } from 'react-use';
|
||||
|
||||
const Demo = () => {
|
||||
const [state, setState] = React.useState('Not called yet');
|
||||
|
||||
function fn() {
|
||||
setState(`called at ${Date.now()}`);
|
||||
}
|
||||
|
||||
const [isReady, cancel, reset] = useTimeoutFn(fn, 5000);
|
||||
const cancelButtonClick = useCallback(() => {
|
||||
if (isReady() === false) {
|
||||
cancel();
|
||||
setState(`cancelled`);
|
||||
} else {
|
||||
reset();
|
||||
setState('Not called yet');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const readyState = isReady();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>{readyState !== null ? 'Function will be called in 5 seconds' : 'Timer cancelled'}</div>
|
||||
<button onClick={cancelButtonClick}> {readyState === false ? 'cancel' : 'restart'} timeout</button>
|
||||
<br />
|
||||
<div>Function state: {readyState === false ? 'Pending' : readyState ? 'Called' : 'Cancelled'}</div>
|
||||
<div>{state}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
```ts
|
||||
const [
|
||||
isReady: () => boolean | null,
|
||||
cancel: () => void,
|
||||
reset: () => void,
|
||||
] = useTimeoutFn(fn: Function, ms: number = 0);
|
||||
```
|
||||
|
||||
- **`fn`**_`: Function`_ - function that will be called;
|
||||
- **`ms`**_`: number`_ - delay in milliseconds;
|
||||
- **`isReady`**_`: ()=>boolean|null`_ - function returning current timeout state:
|
||||
- `false` - pending
|
||||
- `true` - called
|
||||
- `null` - cancelled
|
||||
- **`cancel`**_`: ()=>void`_ - cancel the timeout
|
||||
- **`reset`**_`: ()=>void`_ - reset the timeout
|
||||
|
||||
28
docs/useUpsert.md
Normal file
28
docs/useUpsert.md
Normal file
@ -0,0 +1,28 @@
|
||||
# `useUpsert`
|
||||
|
||||
Superset of `useList`. Provides an additional method to upsert (update or insert) an element into the list.
|
||||
## Usage
|
||||
|
||||
```jsx
|
||||
import {useUpsert} from 'react-use';
|
||||
|
||||
const Demo = () => {
|
||||
const comparisonFunction = (a: DemoType, b: DemoType) => {
|
||||
return a.id === b.id;
|
||||
};
|
||||
const [list, { set, upsert, remove }] = useUpsert(comparisonFunction, initialItems);
|
||||
|
||||
return (
|
||||
<div style={{ display: 'inline-flex', flexDirection: 'column' }}>
|
||||
{list.map((item: DemoType, index: number) => (
|
||||
<div key={item.id}>
|
||||
<input value={item.text} onChange={e => upsert({ ...item, text: e.target.value })} />
|
||||
<button onClick={() => remove(index)}>Remove</button>
|
||||
</div>
|
||||
))}
|
||||
<button onClick={() => upsert({ id: (list.length + 1).toString(), text: '' })}>Add item</button>
|
||||
<button onClick={() => set([])}>Reset</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
13
package.json
13
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-use",
|
||||
"version": "10.6.4",
|
||||
"version": "11.3.1",
|
||||
"description": "Collection of React Hooks",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
@ -51,6 +51,7 @@
|
||||
"react-fast-compare": "^2.0.4",
|
||||
"react-wait": "^0.3.0",
|
||||
"screenfull": "^4.1.0",
|
||||
"set-harmonic-interval": "^1.0.0",
|
||||
"throttle-debounce": "^2.0.1",
|
||||
"ts-easing": "^0.2.0"
|
||||
},
|
||||
@ -73,7 +74,7 @@
|
||||
"@storybook/addon-options": "5.1.11",
|
||||
"@storybook/react": "5.1.11",
|
||||
"@testing-library/react-hooks": "2.0.1",
|
||||
"@types/jest": "24.0.17",
|
||||
"@types/jest": "24.0.18",
|
||||
"@types/react": "16.9.2",
|
||||
"babel-core": "6.26.3",
|
||||
"babel-loader": "8.0.6",
|
||||
@ -83,11 +84,13 @@
|
||||
"husky": "3.0.4",
|
||||
"jest": "24.9.0",
|
||||
"keyboardjs": "2.5.1",
|
||||
"lint-staged": "9.2.3",
|
||||
"lint-staged": "9.2.4",
|
||||
"markdown-loader": "5.1.0",
|
||||
"prettier": "1.18.2",
|
||||
"raf-stub": "3.0.0",
|
||||
"react": "16.9.0",
|
||||
"react-dom": "16.9.0",
|
||||
"react-frame-component": "4.1.1",
|
||||
"react-spring": "8.0.27",
|
||||
"react-test-renderer": "16.9.0",
|
||||
"rebound": "0.1.0",
|
||||
@ -95,10 +98,10 @@
|
||||
"redux-thunk": "2.3.0",
|
||||
"rimraf": "3.0.0",
|
||||
"rxjs": "6.5.2",
|
||||
"semantic-release": "15.13.21",
|
||||
"semantic-release": "15.13.24",
|
||||
"ts-loader": "6.0.4",
|
||||
"ts-node": "8.3.0",
|
||||
"tslint": "5.18.0",
|
||||
"tslint": "5.19.0",
|
||||
"tslint-config-prettier": "1.18.0",
|
||||
"tslint-eslint-rules": "5.4.0",
|
||||
"tslint-plugin-prettier": "2.0.1",
|
||||
|
||||
@ -4,9 +4,36 @@ import { useBattery } from '..';
|
||||
import ShowDocs from './util/ShowDocs';
|
||||
|
||||
const Demo = () => {
|
||||
const state = useBattery();
|
||||
const batteryState = useBattery();
|
||||
|
||||
return <pre>{JSON.stringify(state, null, 2)}</pre>;
|
||||
if (!batteryState.isSupported) {
|
||||
return (
|
||||
<div>
|
||||
<strong>Battery sensor</strong>: <span>not supported</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!batteryState.fetched) {
|
||||
return (
|
||||
<div>
|
||||
<strong>Battery sensor</strong>: <span>supported</span> <br />
|
||||
<strong>Battery state</strong>: <span>fetching</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<strong>Battery sensor</strong>: <span>supported</span> <br />
|
||||
<strong>Battery state</strong>: <span>fetched</span> <br />
|
||||
<strong>Charge level</strong>: <span>{(batteryState.level * 100).toFixed(0)}%</span> <br />
|
||||
<strong>Charging</strong>: <span>{batteryState.charging ? 'yes' : 'no'}</span> <br />
|
||||
<strong>Charging time</strong>:
|
||||
<span>{batteryState.chargingTime ? batteryState.chargingTime : 'finished'}</span> <br />
|
||||
<strong>Discharging time</strong>: <span>{batteryState.dischargingTime}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
storiesOf('Sensors|useBattery', module)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import * as React from 'react';
|
||||
import { useGetSetState, useSetState } from '..';
|
||||
import { useGetSetState } from '..';
|
||||
import ShowDocs from './util/ShowDocs';
|
||||
|
||||
const Demo = () => {
|
||||
|
||||
69
src/__stories__/useHarmonicIntervalFn.story.tsx
Normal file
69
src/__stories__/useHarmonicIntervalFn.story.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import * as React from 'react';
|
||||
import { useHarmonicIntervalFn, useInterval, useTimeoutFn } from '..';
|
||||
import ShowDocs from './util/ShowDocs';
|
||||
|
||||
const Clock: React.FC<{ useInt: typeof useHarmonicIntervalFn }> = ({ useInt }) => {
|
||||
const [count, setCount] = React.useState(0);
|
||||
useInt(() => {
|
||||
setCount(cnt => cnt + 1);
|
||||
}, 1000);
|
||||
|
||||
let m: number | string = Math.floor(count / 60);
|
||||
let s: number | string = count % 60;
|
||||
|
||||
m = m < 10 ? '0' + m : String(m);
|
||||
s = s < 10 ? '0' + s : String(s);
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
padding: '20px 5px',
|
||||
border: '1px solid #fafafa',
|
||||
float: 'left',
|
||||
fontFamily: 'monospace',
|
||||
};
|
||||
|
||||
return <div style={style}>{m + ':' + s}</div>;
|
||||
};
|
||||
|
||||
const Demo: React.FC<{ useInt: typeof useHarmonicIntervalFn }> = ({ useInt }) => {
|
||||
const [showSecondClock, setShowSecondClock] = React.useState(false);
|
||||
useTimeoutFn(() => {
|
||||
setShowSecondClock(true);
|
||||
}, 500);
|
||||
|
||||
const headingStyle: React.CSSProperties = {
|
||||
fontFamily: 'sans',
|
||||
fontSize: '20px',
|
||||
padding: '0',
|
||||
lineHeight: '1.5em',
|
||||
};
|
||||
|
||||
const rowStyle: React.CSSProperties = {
|
||||
width: '100%',
|
||||
clear: 'both',
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={rowStyle}>
|
||||
<h2 style={headingStyle}>{useInt.name}</h2>
|
||||
<Clock useInt={useInt} />
|
||||
{showSecondClock ? <Clock useInt={useInt} /> : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
storiesOf('Animation|useHarmonicIntervalFn', module)
|
||||
.add('Docs', () => <ShowDocs md={require('../../docs/useHarmonicIntervalFn.md')} />)
|
||||
.add('Demo', () => (
|
||||
<>
|
||||
<Demo useInt={useInterval} />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<Demo useInt={useHarmonicIntervalFn} />
|
||||
</>
|
||||
));
|
||||
@ -28,6 +28,6 @@ const Demo = () => {
|
||||
);
|
||||
};
|
||||
|
||||
storiesOf('Side effects|useInterval', module)
|
||||
storiesOf('Animation|useInterval', module)
|
||||
.add('Docs', () => <ShowDocs md={require('../../docs/useInterval.md')} />)
|
||||
.add('Demo', () => <Demo />);
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import * as React from 'react';
|
||||
import { useRef } from 'react';
|
||||
import Frame from 'react-frame-component';
|
||||
import { useLockBodyScroll, useToggle } from '..';
|
||||
import ShowDocs from './util/ShowDocs';
|
||||
|
||||
@ -27,6 +29,30 @@ const AnotherComponent = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const IframeComponent = () => {
|
||||
const [mainLocked, toggleMainLocked] = useToggle(false);
|
||||
const [iframeLocked, toggleIframeLocked] = useToggle(false);
|
||||
const iframeElementRef = useRef<HTMLIFrameElement>(null);
|
||||
|
||||
useLockBodyScroll(mainLocked);
|
||||
useLockBodyScroll(iframeLocked, iframeElementRef);
|
||||
|
||||
return (
|
||||
<div style={{ height: '200vh' }}>
|
||||
<Frame style={{ height: '50vh', width: '50vw' }}>
|
||||
<div style={{ height: '200vh' }} ref={iframeElementRef}>
|
||||
<button onClick={() => toggleMainLocked()} style={{ position: 'fixed', left: 0, top: 0 }}>
|
||||
{mainLocked ? 'Unlock' : 'Lock'} main window scroll
|
||||
</button>
|
||||
<button onClick={() => toggleIframeLocked()} style={{ position: 'fixed', left: 0, top: 64 }}>
|
||||
{iframeLocked ? 'Unlock' : 'Lock'} iframe window scroll
|
||||
</button>
|
||||
</div>
|
||||
</Frame>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
storiesOf('Side effects|useLockBodyScroll', module)
|
||||
.add('Docs', () => <ShowDocs md={require('../../docs/useLockBodyScroll.md')} />)
|
||||
.add('Demo', () => <Demo />)
|
||||
@ -34,5 +60,7 @@ storiesOf('Side effects|useLockBodyScroll', module)
|
||||
<>
|
||||
<AnotherComponent />
|
||||
<Demo />
|
||||
<IframeComponent />
|
||||
</>
|
||||
));
|
||||
))
|
||||
.add('Iframe', () => <IframeComponent />);
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { boolean, text, withKnobs } from '@storybook/addon-knobs';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import * as React from 'react';
|
||||
import { useCounter } from '..';
|
||||
import { useLogger } from '..';
|
||||
import { useCounter, useLogger } from '..';
|
||||
import ShowDocs from './util/ShowDocs';
|
||||
|
||||
const Demo = props => {
|
||||
|
||||
23
src/__stories__/usePreviousDistinct.story.tsx
Normal file
23
src/__stories__/usePreviousDistinct.story.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import * as React from 'react';
|
||||
import { usePreviousDistinct, useCounter } from '..';
|
||||
import ShowDocs from './util/ShowDocs';
|
||||
|
||||
const Demo = () => {
|
||||
const [count, { inc: relatedInc }] = useCounter(0);
|
||||
const [unrelatedCount, { inc }] = useCounter(0);
|
||||
const prevCount = usePreviousDistinct(count);
|
||||
|
||||
return (
|
||||
<p>
|
||||
Now: {count}, before: {prevCount}
|
||||
<button onClick={() => relatedInc()}>Increment</button>
|
||||
Unrelated: {unrelatedCount}
|
||||
<button onClick={() => inc()}>Increment Unrelated</button>
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
storiesOf('State|usePreviousDistinct', module)
|
||||
.add('Docs', () => <ShowDocs md={require('../../docs/usePreviousDistinct.md')} />)
|
||||
.add('Demo', () => <Demo />);
|
||||
@ -1,7 +1,7 @@
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import * as React from 'react';
|
||||
import ShowDocs from './util/ShowDocs';
|
||||
import { useRafLoop } from '..';
|
||||
import ShowDocs from './util/ShowDocs';
|
||||
|
||||
const Demo = () => {
|
||||
const [ticks, setTicks] = React.useState(0);
|
||||
|
||||
@ -5,8 +5,15 @@ import ShowDocs from './util/ShowDocs';
|
||||
|
||||
const Demo = () => {
|
||||
const refMounted = useRefMounted();
|
||||
|
||||
useRaf();
|
||||
return <div>is mounted: {refMounted.current ? '👍' : '👎'}</div>;
|
||||
return (
|
||||
<div>
|
||||
<h3>**DEPRECATED**</h3>
|
||||
<h4>This method is obsolete, use `useMountedState` instead.</h4>
|
||||
<span>is mounted: {refMounted.current ? '👍' : '👎'}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
storiesOf('Lifecycle|useRefMounted', module)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import * as React from 'react';
|
||||
import { useSpring } from '..';
|
||||
import useSpring from '../useSpring';
|
||||
import ShowDocs from './util/ShowDocs';
|
||||
|
||||
const Demo = () => {
|
||||
|
||||
22
src/__stories__/useStateList.story.tsx
Normal file
22
src/__stories__/useStateList.story.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import * as React from 'react';
|
||||
import { useStateList } from '..';
|
||||
import ShowDocs from './util/ShowDocs';
|
||||
|
||||
const stateSet = ['first', 'second', 'third', 'fourth', 'fifth'];
|
||||
|
||||
const Demo = () => {
|
||||
const { state, prev, next } = useStateList(stateSet);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<pre>{state}</pre>
|
||||
<button onClick={() => prev()}>prev</button>
|
||||
<button onClick={() => next()}>next</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
storiesOf('State|useStateList', module)
|
||||
.add('Docs', () => <ShowDocs md={require('../../docs/useStateList.md')} />)
|
||||
.add('Demo', () => <Demo />);
|
||||
@ -3,10 +3,25 @@ import * as React from 'react';
|
||||
import { useTimeout } from '..';
|
||||
import ShowDocs from './util/ShowDocs';
|
||||
|
||||
const Demo = () => {
|
||||
const ready = useTimeout(2e3);
|
||||
function TestComponent(props: { ms?: number } = {}) {
|
||||
const ms = props.ms || 5000;
|
||||
const [isReady, cancel] = useTimeout(ms);
|
||||
|
||||
return <div>Ready: {ready ? 'Yes' : 'No'}</div>;
|
||||
return (
|
||||
<div>
|
||||
{isReady() ? "I'm reloaded after timeout" : `I will be reloaded after ${ms / 1000}s`}
|
||||
{isReady() === false ? <button onClick={cancel}>Cancel</button> : ''}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Demo = () => {
|
||||
return (
|
||||
<div>
|
||||
<TestComponent />
|
||||
<TestComponent ms={10000} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
storiesOf('Animation|useTimeout', module)
|
||||
|
||||
40
src/__stories__/useTimeoutFn.story.tsx
Normal file
40
src/__stories__/useTimeoutFn.story.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import * as React from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useTimeoutFn } from '../index';
|
||||
import ShowDocs from './util/ShowDocs';
|
||||
|
||||
const Demo = () => {
|
||||
const [state, setState] = React.useState('Not called yet');
|
||||
|
||||
function fn() {
|
||||
setState(`called at ${Date.now()}`);
|
||||
}
|
||||
|
||||
const [isReady, cancel, reset] = useTimeoutFn(fn, 5000);
|
||||
const cancelButtonClick = useCallback(() => {
|
||||
if (isReady() === false) {
|
||||
cancel();
|
||||
setState(`cancelled`);
|
||||
} else {
|
||||
reset();
|
||||
setState('Not called yet');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const readyState = isReady();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>{readyState !== null ? 'Function will be called in 5 seconds' : 'Timer cancelled'}</div>
|
||||
<button onClick={cancelButtonClick}> {readyState === false ? 'cancel' : 'restart'} timeout</button>
|
||||
<br />
|
||||
<div>Function state: {readyState === false ? 'Pending' : readyState ? 'Called' : 'Cancelled'}</div>
|
||||
<div>{state}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
storiesOf('Animation|useTimeoutFn', module)
|
||||
.add('Docs', () => <ShowDocs md={require('../../docs/useTimeoutFn.md')} />)
|
||||
.add('Demo', () => <Demo />);
|
||||
35
src/__stories__/useUpsert.story.tsx
Normal file
35
src/__stories__/useUpsert.story.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import * as React from 'react';
|
||||
import { useUpsert } from '../index';
|
||||
import ShowDocs from './util/ShowDocs';
|
||||
|
||||
interface DemoType {
|
||||
id: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
const initialItems: DemoType[] = [{ id: '1', text: 'Sample' }, { id: '2', text: '' }];
|
||||
|
||||
const Demo = () => {
|
||||
const comparisonFunction = (a: DemoType, b: DemoType) => {
|
||||
return a.id === b.id;
|
||||
};
|
||||
const [list, { set, upsert, remove }] = useUpsert(comparisonFunction, initialItems);
|
||||
|
||||
return (
|
||||
<div style={{ display: 'inline-flex', flexDirection: 'column' }}>
|
||||
{list.map((item: DemoType, index: number) => (
|
||||
<div key={item.id}>
|
||||
<input value={item.text} onChange={e => upsert({ ...item, text: e.target.value })} />
|
||||
<button onClick={() => remove(index)}>Remove</button>
|
||||
</div>
|
||||
))}
|
||||
<button onClick={() => upsert({ id: (list.length + 1).toString(), text: '' })}>Add item</button>
|
||||
<button onClick={() => set([])}>Reset</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
storiesOf('State|useUpsert', module)
|
||||
.add('Docs', () => <ShowDocs md={require('../../docs/useUpsert.md')} />)
|
||||
.add('Demo', () => <Demo />);
|
||||
@ -1,7 +1,7 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import createReducer from '../createReducer';
|
||||
import logger from 'redux-logger';
|
||||
import thunk from 'redux-thunk';
|
||||
import createReducer from '../createReducer';
|
||||
|
||||
it('should init reducer hook function', () => {
|
||||
const useSomeReducer = createReducer();
|
||||
|
||||
@ -107,11 +107,14 @@ describe('useAsync', () => {
|
||||
return 'new value';
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(done => {
|
||||
callCount = 0;
|
||||
|
||||
hook = renderHook(({ fn }) => useAsync(fn, [fn]), {
|
||||
initialProps: { fn: initialFn },
|
||||
});
|
||||
|
||||
hook.waitForNextUpdate().then(done);
|
||||
});
|
||||
|
||||
it('renders the first value', () => {
|
||||
@ -140,7 +143,7 @@ describe('useAsync', () => {
|
||||
return `counter is ${counter} and callCount is ${callCount}`;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(done => {
|
||||
callCount = 0;
|
||||
hook = renderHook(
|
||||
({ fn, counter }) => {
|
||||
@ -154,6 +157,8 @@ describe('useAsync', () => {
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
hook.waitForNextUpdate().then(done);
|
||||
});
|
||||
|
||||
it('initial renders the first passed pargs', () => {
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
// does not automatically invoke the function
|
||||
// and it can take arguments.
|
||||
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import useAsyncFn, { AsyncState } from '../useAsyncFn';
|
||||
|
||||
type AdderFn = (a: number, b: number) => Promise<number>;
|
||||
@ -17,27 +17,26 @@ describe('useAsyncFn', () => {
|
||||
|
||||
describe('the callback can be awaited and return the value', () => {
|
||||
let hook;
|
||||
let callCount = 0;
|
||||
const adder = async (a: number, b: number): Promise<number> => {
|
||||
callCount++;
|
||||
return a + b;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// NOTE: renderHook isn't good at inferring array types
|
||||
hook = renderHook<{ fn: AdderFn }, [AsyncState<number>, AdderFn]>(({ fn }) => useAsyncFn(fn), {
|
||||
initialProps: {
|
||||
fn: adder,
|
||||
},
|
||||
initialProps: { fn: adder },
|
||||
});
|
||||
});
|
||||
|
||||
it('awaits the result', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const [s, callback] = hook.result.current;
|
||||
const [, callback] = hook.result.current;
|
||||
let result;
|
||||
|
||||
const result = await callback(5, 7);
|
||||
await act(async () => {
|
||||
result = await callback(5, 7);
|
||||
});
|
||||
|
||||
expect(result).toEqual(12);
|
||||
|
||||
@ -78,13 +77,15 @@ describe('useAsyncFn', () => {
|
||||
it('resolves a value derived from args', async () => {
|
||||
expect.assertions(4);
|
||||
|
||||
const [s, callback] = hook.result.current;
|
||||
const [, callback] = hook.result.current;
|
||||
|
||||
callback(2, 7);
|
||||
act(() => {
|
||||
callback(2, 7);
|
||||
});
|
||||
hook.rerender({ fn: adder });
|
||||
await hook.waitForNextUpdate();
|
||||
|
||||
const [state, c] = hook.result.current;
|
||||
const [state] = hook.result.current;
|
||||
|
||||
expect(callCount).toEqual(1);
|
||||
expect(state.loading).toEqual(false);
|
||||
|
||||
@ -100,8 +100,7 @@ it('should log an error if set with a patch different than an object', () => {
|
||||
const [, set] = result.current;
|
||||
expect(mockConsoleError).not.toHaveBeenCalled();
|
||||
|
||||
// @ts-ignore
|
||||
act(() => set('not an object'));
|
||||
act(() => set('not an object' as any));
|
||||
|
||||
expect(mockConsoleError).toHaveBeenCalledTimes(1);
|
||||
expect(mockConsoleError).toHaveBeenCalledWith('useGetSetState setter patch must be an object.');
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import useMap from '../useMap';
|
||||
|
||||
const setUp = (initialMap?: object) => renderHook(() => useMap(initialMap));
|
||||
const setUp = <T extends object>(initialMap?: T) => renderHook(() => useMap(initialMap));
|
||||
|
||||
it('should init map and utils', () => {
|
||||
const { result } = setUp({ foo: 'bar', a: 1 });
|
||||
@ -28,7 +28,6 @@ it('should get corresponding value for existing provided key', () => {
|
||||
|
||||
let value;
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
value = utils.get('a');
|
||||
});
|
||||
|
||||
@ -36,12 +35,11 @@ it('should get corresponding value for existing provided key', () => {
|
||||
});
|
||||
|
||||
it('should get undefined for non-existing provided key', () => {
|
||||
const { result } = setUp({ foo: 'bar', a: 1 });
|
||||
const { result } = setUp<{ foo: string; a: number; nonExisting?: any }>({ foo: 'bar', a: 1 });
|
||||
const [, utils] = result.current;
|
||||
|
||||
let value;
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
value = utils.get('nonExisting');
|
||||
});
|
||||
|
||||
@ -49,11 +47,10 @@ it('should get undefined for non-existing provided key', () => {
|
||||
});
|
||||
|
||||
it('should set new key-value pair', () => {
|
||||
const { result } = setUp({ foo: 'bar', a: 1 });
|
||||
const { result } = setUp<{ foo: string; a: number; newKey?: number }>({ foo: 'bar', a: 1 });
|
||||
const [, utils] = result.current;
|
||||
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
utils.set('newKey', 99);
|
||||
});
|
||||
|
||||
@ -65,11 +62,10 @@ it('should override current value if setting existing key', () => {
|
||||
const [, utils] = result.current;
|
||||
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
utils.set('foo', 99);
|
||||
utils.set('foo', 'qux');
|
||||
});
|
||||
|
||||
expect(result.current[0]).toEqual({ foo: 99, a: 1 });
|
||||
expect(result.current[0]).toEqual({ foo: 'qux', a: 1 });
|
||||
});
|
||||
|
||||
it('should remove corresponding key-value pair for existing provided key', () => {
|
||||
@ -77,7 +73,6 @@ it('should remove corresponding key-value pair for existing provided key', () =>
|
||||
const [, utils] = result.current;
|
||||
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
utils.remove('foo');
|
||||
});
|
||||
|
||||
@ -85,11 +80,10 @@ it('should remove corresponding key-value pair for existing provided key', () =>
|
||||
});
|
||||
|
||||
it('should do nothing if removing non-existing provided key', () => {
|
||||
const { result } = setUp({ foo: 'bar', a: 1 });
|
||||
const { result } = setUp<{ foo: string; a: number; nonExisting?: any }>({ foo: 'bar', a: 1 });
|
||||
const [, utils] = result.current;
|
||||
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
utils.remove('nonExisting');
|
||||
});
|
||||
|
||||
@ -97,11 +91,10 @@ it('should do nothing if removing non-existing provided key', () => {
|
||||
});
|
||||
|
||||
it('should reset map to initial object provided', () => {
|
||||
const { result } = setUp({ foo: 'bar', a: 1 });
|
||||
const { result } = setUp<{ foo: string; a: number; z?: number }>({ foo: 'bar', a: 1 });
|
||||
const [, utils] = result.current;
|
||||
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
utils.set('z', 99);
|
||||
});
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import useNumber from '../useNumber';
|
||||
import useCounter from '../useCounter';
|
||||
import useNumber from '../useNumber';
|
||||
|
||||
it('should be an alias for useCounter', () => {
|
||||
expect(useNumber).toBe(useCounter);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { Subject } from 'rxjs';
|
||||
import useObservable, { Observable } from '../useObservable';
|
||||
import * as useIsomorphicLayoutEffect from '../useIsomorphicLayoutEffect';
|
||||
import useObservable, { Observable } from '../useObservable';
|
||||
|
||||
const setUp = (observable: Observable<any>, initialValue?: any) =>
|
||||
renderHook(() => useObservable(observable, initialValue));
|
||||
|
||||
65
src/__tests__/usePreviousDistinct.test.tsx
Normal file
65
src/__tests__/usePreviousDistinct.test.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import usePreviousDistinct from '../usePreviousDistinct';
|
||||
|
||||
describe('usePreviousDistinct with default compare', () => {
|
||||
const hook = renderHook(props => usePreviousDistinct(props), { initialProps: 0 });
|
||||
|
||||
it('should return undefined on initial render', () => {
|
||||
expect(hook.result.current).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should return previous state only after a different value is rendered', () => {
|
||||
expect(hook.result.current).toBeUndefined();
|
||||
hook.rerender(1);
|
||||
expect(hook.result.current).toBe(0);
|
||||
hook.rerender(2);
|
||||
hook.rerender(2);
|
||||
expect(hook.result.current).toBe(1);
|
||||
|
||||
hook.rerender(3);
|
||||
expect(hook.result.current).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('usePreviousDistinct with complex comparison', () => {
|
||||
const exampleObjects = [
|
||||
{
|
||||
id: 'something-unique',
|
||||
name: 'Nancy',
|
||||
},
|
||||
{
|
||||
id: 'something-unique2',
|
||||
name: 'Fred',
|
||||
},
|
||||
{
|
||||
id: 'something-unique3',
|
||||
name: 'Bill',
|
||||
},
|
||||
{
|
||||
id: 'something-unique4',
|
||||
name: 'Alice',
|
||||
},
|
||||
];
|
||||
const hook = renderHook(
|
||||
props => usePreviousDistinct(props, (prev, next) => (prev && prev.id) === (next && next.id)),
|
||||
{
|
||||
initialProps: exampleObjects[0],
|
||||
}
|
||||
);
|
||||
|
||||
it('should return undefined on initial render', () => {
|
||||
expect(hook.result.current).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should return previous state only after a different value is rendered', () => {
|
||||
expect(hook.result.current).toBeUndefined();
|
||||
hook.rerender(exampleObjects[1]);
|
||||
expect(hook.result.current).toMatchObject(exampleObjects[0]);
|
||||
hook.rerender(exampleObjects[2]);
|
||||
hook.rerender(exampleObjects[2]);
|
||||
expect(hook.result.current).toMatchObject(exampleObjects[1]);
|
||||
|
||||
hook.rerender(exampleObjects[3]);
|
||||
expect(hook.result.current).toMatchObject(exampleObjects[2]);
|
||||
});
|
||||
});
|
||||
155
src/__tests__/useRaf.test.ts
Normal file
155
src/__tests__/useRaf.test.ts
Normal file
@ -0,0 +1,155 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { replaceRaf } from 'raf-stub';
|
||||
import useRaf from '../useRaf';
|
||||
|
||||
/**
|
||||
* New requestAnimationFrame after being replaced with raf-stub for testing purposes.
|
||||
*/
|
||||
interface RequestAnimationFrame {
|
||||
reset(): void;
|
||||
|
||||
step(): void;
|
||||
}
|
||||
|
||||
declare var requestAnimationFrame: RequestAnimationFrame;
|
||||
|
||||
replaceRaf();
|
||||
const fixedStart = 1564949709496;
|
||||
const spyDateNow = jest.spyOn(Date, 'now').mockImplementation(() => fixedStart);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
requestAnimationFrame.reset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllTimers();
|
||||
requestAnimationFrame.reset();
|
||||
});
|
||||
|
||||
it('should init percentage of time elapsed', () => {
|
||||
const { result } = renderHook(() => useRaf());
|
||||
const timeElapsed = result.current;
|
||||
|
||||
expect(timeElapsed).toBe(0);
|
||||
});
|
||||
|
||||
it('should return corresponding percentage of time elapsed for default ms', () => {
|
||||
const { result } = renderHook(() => useRaf());
|
||||
expect(result.current).toBe(0);
|
||||
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers(); // start after delay
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + 1e12 * 0.25); // 25%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(0.25);
|
||||
|
||||
act(() => {
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + 1e12 * 0.5); // 50%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(0.5);
|
||||
|
||||
act(() => {
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + 1e12 * 0.75); // 75%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(0.75);
|
||||
|
||||
act(() => {
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + 1e12); // 100%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(1);
|
||||
});
|
||||
|
||||
it('should return corresponding percentage of time elapsed for custom ms', () => {
|
||||
const customMs = 2000;
|
||||
|
||||
const { result } = renderHook(() => useRaf(customMs));
|
||||
expect(result.current).toBe(0);
|
||||
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers(); // start after delay
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + customMs * 0.25); // 25%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(0.25);
|
||||
|
||||
act(() => {
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + customMs * 0.5); // 50%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(0.5);
|
||||
|
||||
act(() => {
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + customMs * 0.75); // 75%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(0.75);
|
||||
|
||||
act(() => {
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + customMs); // 100%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(1);
|
||||
});
|
||||
|
||||
it('should return always 1 after corresponding ms reached', () => {
|
||||
const { result } = renderHook(() => useRaf());
|
||||
expect(result.current).toBe(0);
|
||||
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers(); // start after delay
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + 1e12); // 100%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(1);
|
||||
|
||||
act(() => {
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + 1e12 * 1.1); // 110%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(1);
|
||||
|
||||
act(() => {
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + 1e12 * 3); // 300%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(1);
|
||||
});
|
||||
|
||||
it('should wait until delay reached to start calculating elapsed percentage', () => {
|
||||
const { result } = renderHook(() => useRaf(undefined, 500));
|
||||
|
||||
expect(result.current).toBe(0);
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(250); // fast-forward only half of custom delay
|
||||
});
|
||||
expect(result.current).toBe(0);
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(249); // fast-forward 1ms less than custom delay
|
||||
});
|
||||
expect(result.current).toBe(0);
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(1); // fast-forward exactly to custom delay
|
||||
});
|
||||
expect(result.current).not.toBe(0);
|
||||
});
|
||||
|
||||
it('should clear pending timers on unmount', () => {
|
||||
const spyRafStop = jest.spyOn(global, 'cancelAnimationFrame' as any);
|
||||
const { unmount } = renderHook(() => useRaf());
|
||||
|
||||
expect(clearTimeout).not.toHaveBeenCalled();
|
||||
expect(spyRafStop).not.toHaveBeenCalled();
|
||||
|
||||
unmount();
|
||||
|
||||
expect(clearTimeout).toHaveBeenCalledTimes(2);
|
||||
expect(spyRafStop).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@ -1,23 +1,26 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { replaceRaf } from 'raf-stub';
|
||||
import useRafLoop from '../useRafLoop';
|
||||
|
||||
declare var requestAnimationFrame: {
|
||||
add: (cb: Function) => number;
|
||||
remove: (id: number) => void;
|
||||
flush: (duration?: number) => void;
|
||||
reset: () => void;
|
||||
step: (steps?: number, duration?: number) => void;
|
||||
};
|
||||
|
||||
describe('useRafLoop', () => {
|
||||
it('should be defined', () => {
|
||||
expect(useRafLoop).toBeDefined();
|
||||
beforeAll(() => {
|
||||
replaceRaf();
|
||||
});
|
||||
|
||||
it('should call a callback constantly inside the raf loop', done => {
|
||||
let calls = 0;
|
||||
const spy = () => calls++;
|
||||
renderHook(() => useRafLoop(spy), { initialProps: false });
|
||||
afterEach(() => {
|
||||
requestAnimationFrame.reset();
|
||||
});
|
||||
|
||||
expect(calls).toEqual(0);
|
||||
|
||||
setTimeout(() => {
|
||||
expect(calls).toBeGreaterThanOrEqual(2);
|
||||
|
||||
done();
|
||||
}, 120);
|
||||
it('should be defined', () => {
|
||||
expect(useRafLoop).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return stop function, start function and loop state', () => {
|
||||
@ -28,26 +31,31 @@ describe('useRafLoop', () => {
|
||||
expect(typeof hook.result.current[2]).toEqual('function');
|
||||
});
|
||||
|
||||
it('first element call should stop the loop', done => {
|
||||
let calls = 0;
|
||||
const spy = () => calls++;
|
||||
it('should call a callback constantly inside the raf loop', () => {
|
||||
const spy = jest.fn();
|
||||
renderHook(() => useRafLoop(spy), { initialProps: false });
|
||||
|
||||
expect(spy).not.toBeCalled();
|
||||
requestAnimationFrame.step();
|
||||
requestAnimationFrame.step();
|
||||
expect(spy).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it('first element call should stop the loop', () => {
|
||||
const spy = jest.fn();
|
||||
const hook = renderHook(() => useRafLoop(spy), { initialProps: false });
|
||||
|
||||
// stop the loop
|
||||
expect(spy).not.toBeCalled();
|
||||
|
||||
act(() => {
|
||||
hook.result.current[0]();
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(calls).toEqual(0);
|
||||
|
||||
done();
|
||||
}, 50);
|
||||
requestAnimationFrame.step();
|
||||
expect(spy).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('second element should represent loop state', done => {
|
||||
let calls = 0;
|
||||
const spy = () => calls++;
|
||||
it('second element should represent loop state', () => {
|
||||
const spy = jest.fn();
|
||||
const hook = renderHook(() => useRafLoop(spy), { initialProps: false });
|
||||
|
||||
expect(hook.result.current[1]).toBe(true);
|
||||
@ -56,56 +64,39 @@ describe('useRafLoop', () => {
|
||||
act(() => {
|
||||
hook.result.current[0]();
|
||||
});
|
||||
|
||||
expect(hook.result.current[1]).toBe(false);
|
||||
setTimeout(() => {
|
||||
expect(calls).toEqual(0);
|
||||
|
||||
done();
|
||||
}, 120);
|
||||
});
|
||||
|
||||
it('third element call should restart loop', done => {
|
||||
let calls = 0;
|
||||
const spy = () => calls++;
|
||||
it('third element call should restart loop', () => {
|
||||
const spy = jest.fn();
|
||||
const hook = renderHook(() => useRafLoop(spy), { initialProps: false });
|
||||
|
||||
expect(hook.result.current[1]).toBe(true);
|
||||
|
||||
expect(spy).not.toBeCalled();
|
||||
// stop the loop
|
||||
act(() => {
|
||||
hook.result.current[0]();
|
||||
});
|
||||
requestAnimationFrame.step();
|
||||
expect(spy).not.toBeCalled();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(hook.result.current[1]).toBe(false);
|
||||
expect(calls).toEqual(0);
|
||||
// start the loop
|
||||
act(() => {
|
||||
hook.result.current[2]();
|
||||
});
|
||||
|
||||
// start the loop
|
||||
act(() => {
|
||||
hook.result.current[2]();
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(hook.result.current[1]).toBe(true);
|
||||
expect(calls).toBeGreaterThanOrEqual(2);
|
||||
|
||||
done();
|
||||
}, 120);
|
||||
}, 50);
|
||||
requestAnimationFrame.step();
|
||||
requestAnimationFrame.step();
|
||||
expect(spy).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it('loop should stop itself on unmount', done => {
|
||||
let calls = 0;
|
||||
const spy = () => calls++;
|
||||
it('loop should stop itself on unmount', () => {
|
||||
const spy = jest.fn();
|
||||
const hook = renderHook(() => useRafLoop(spy), { initialProps: false });
|
||||
|
||||
hook.unmount();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(calls).toEqual(0);
|
||||
requestAnimationFrame.step();
|
||||
|
||||
done();
|
||||
}, 50);
|
||||
expect(spy).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@ -40,3 +40,21 @@ it('should merge changes into current state when providing function', () => {
|
||||
|
||||
expect(result.current[0]).toEqual({ foo: 'bar', count: 2, someBool: true });
|
||||
});
|
||||
|
||||
/**
|
||||
* Enforces cases where a hook can safely depend on the callback without
|
||||
* causing an endless rerender cycle: useEffect(() => setState({ data }), [setState]);
|
||||
*/
|
||||
it('should return a memoized setState callback', () => {
|
||||
const { result, rerender } = setUp({ ok: false });
|
||||
const [, setState1] = result.current;
|
||||
|
||||
act(() => {
|
||||
setState1({ ok: true });
|
||||
});
|
||||
rerender();
|
||||
|
||||
const [, setState2] = result.current;
|
||||
|
||||
expect(setState1).toBe(setState2);
|
||||
});
|
||||
|
||||
119
src/__tests__/useSpring.test.ts
Normal file
119
src/__tests__/useSpring.test.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { Spring } from 'rebound';
|
||||
import useSpring from '../useSpring';
|
||||
|
||||
// simulate Spring for testing
|
||||
const mockSetCurrentValue = jest.fn();
|
||||
const mockAddListener = jest.fn();
|
||||
const mockSetEndValue = jest.fn();
|
||||
const mockRemoveListener = jest.fn();
|
||||
let triggerSpringUpdate = () => {};
|
||||
let springListener: Listener = { onSpringUpdate: () => {} };
|
||||
|
||||
interface Listener {
|
||||
onSpringUpdate: (currentSpring: Spring) => void;
|
||||
}
|
||||
|
||||
const mockCreateSpring: Spring = jest.fn().mockImplementation(() => {
|
||||
let currentValue = 0;
|
||||
let endValue = 0;
|
||||
|
||||
const getCloserValue = (a, b) => Math.round((a + b) / 2);
|
||||
|
||||
const getCurrentValue = () => {
|
||||
currentValue = getCloserValue(currentValue, endValue);
|
||||
return currentValue;
|
||||
};
|
||||
|
||||
triggerSpringUpdate = () => {
|
||||
if (currentValue !== endValue) {
|
||||
springListener.onSpringUpdate({ getCurrentValue } as any);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
setCurrentValue: val => {
|
||||
currentValue = val;
|
||||
mockSetCurrentValue(val);
|
||||
},
|
||||
addListener: newListener => {
|
||||
springListener = newListener;
|
||||
mockAddListener(newListener);
|
||||
},
|
||||
setEndValue: val => {
|
||||
endValue = val;
|
||||
mockSetEndValue(val);
|
||||
},
|
||||
removeListener: mockRemoveListener,
|
||||
};
|
||||
}) as any;
|
||||
|
||||
jest.mock('rebound', () => {
|
||||
return {
|
||||
Sprint: {},
|
||||
SpringSystem: jest.fn().mockImplementation(() => {
|
||||
return { createSpring: mockCreateSpring };
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
it('should init value to provided target', () => {
|
||||
const { result } = renderHook(() => useSpring(70));
|
||||
|
||||
expect(result.current).toBe(70);
|
||||
expect(mockSetCurrentValue).toHaveBeenCalledTimes(1);
|
||||
expect(mockSetCurrentValue).toHaveBeenCalledWith(70);
|
||||
expect(mockCreateSpring).toHaveBeenCalledTimes(1);
|
||||
expect(mockCreateSpring).toHaveBeenCalledWith(50, 3);
|
||||
});
|
||||
|
||||
it('should create spring with custom tension and friction args provided', () => {
|
||||
renderHook(() => useSpring(500, 20, 7));
|
||||
|
||||
expect(mockCreateSpring).toHaveBeenCalledTimes(1);
|
||||
expect(mockCreateSpring).toHaveBeenCalledWith(20, 7);
|
||||
});
|
||||
|
||||
it('should subscribe only once', () => {
|
||||
const { rerender } = renderHook(() => useSpring());
|
||||
|
||||
expect(mockAddListener).toHaveBeenCalledTimes(1);
|
||||
expect(mockAddListener).toHaveBeenCalledWith(springListener);
|
||||
|
||||
rerender();
|
||||
|
||||
expect(mockAddListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle spring update', () => {
|
||||
let targetValue = 70;
|
||||
let lastSpringValue = targetValue;
|
||||
const { result, rerender } = renderHook(() => useSpring(targetValue));
|
||||
|
||||
targetValue = 100;
|
||||
rerender();
|
||||
expect(result.current).toBe(lastSpringValue);
|
||||
|
||||
act(() => {
|
||||
triggerSpringUpdate(); // simulate new spring value
|
||||
});
|
||||
expect(result.current).toBeGreaterThan(lastSpringValue);
|
||||
expect(result.current).toBeLessThanOrEqual(targetValue);
|
||||
|
||||
lastSpringValue = result.current;
|
||||
act(() => {
|
||||
triggerSpringUpdate(); // simulate another new spring value
|
||||
});
|
||||
expect(result.current).toBeGreaterThan(lastSpringValue);
|
||||
expect(result.current).toBeLessThanOrEqual(targetValue);
|
||||
});
|
||||
|
||||
it('should remove listener on unmount', () => {
|
||||
const { unmount } = renderHook(() => useSpring());
|
||||
expect(mockRemoveListener).not.toHaveBeenCalled();
|
||||
|
||||
unmount();
|
||||
|
||||
expect(mockRemoveListener).toHaveBeenCalledTimes(1);
|
||||
expect(mockRemoveListener).toHaveBeenCalledWith(springListener);
|
||||
});
|
||||
144
src/__tests__/useStateList.test.ts
Normal file
144
src/__tests__/useStateList.test.ts
Normal file
@ -0,0 +1,144 @@
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import useStateList from '../useStateList';
|
||||
|
||||
const callNext = hook => {
|
||||
act(() => {
|
||||
const { next } = hook.result.current;
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
const callPrev = hook => {
|
||||
act(() => {
|
||||
const { prev } = hook.result.current;
|
||||
prev();
|
||||
});
|
||||
};
|
||||
|
||||
describe('happy flow', () => {
|
||||
const hook = renderHook(({ stateSet }) => useStateList(stateSet), {
|
||||
initialProps: {
|
||||
stateSet: ['a', 'b', 'c'],
|
||||
},
|
||||
});
|
||||
|
||||
it('should return the first state on initial render', () => {
|
||||
const { state } = hook.result.current;
|
||||
expect(state).toBe('a');
|
||||
});
|
||||
|
||||
it('should return the second state after calling the "next" function', () => {
|
||||
callNext(hook);
|
||||
|
||||
const { state } = hook.result.current;
|
||||
expect(state).toBe('b');
|
||||
});
|
||||
|
||||
it('should return the first state again after calling the "next" function "stateSet.length" times', () => {
|
||||
callNext(hook);
|
||||
callNext(hook);
|
||||
|
||||
const { state } = hook.result.current;
|
||||
expect(state).toBe('a');
|
||||
});
|
||||
|
||||
it('should return the last state again after calling the "prev" function', () => {
|
||||
callPrev(hook);
|
||||
|
||||
const { state } = hook.result.current;
|
||||
expect(state).toBe('c');
|
||||
});
|
||||
|
||||
it('should return the previous state after calling the "prev" function', () => {
|
||||
callPrev(hook);
|
||||
|
||||
const { state } = hook.result.current;
|
||||
expect(state).toBe('b');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with empty state set', () => {
|
||||
const hook = renderHook(({ stateSet }) => useStateList(stateSet), {
|
||||
initialProps: {
|
||||
stateSet: [],
|
||||
},
|
||||
});
|
||||
|
||||
it('should return undefined on initial render', () => {
|
||||
const { state } = hook.result.current;
|
||||
expect(state).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should always return undefined (calling next)', () => {
|
||||
callNext(hook);
|
||||
|
||||
const { state } = hook.result.current;
|
||||
expect(state).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should always return undefined (calling prev)', () => {
|
||||
callPrev(hook);
|
||||
|
||||
const { state } = hook.result.current;
|
||||
expect(state).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a single state set', () => {
|
||||
const hook = renderHook(({ stateSet }) => useStateList(stateSet), {
|
||||
initialProps: {
|
||||
stateSet: ['a'],
|
||||
},
|
||||
});
|
||||
|
||||
it('should return "a" on initial render', () => {
|
||||
const { state } = hook.result.current;
|
||||
expect(state).toBe('a');
|
||||
});
|
||||
|
||||
it('should always return "a" (calling next)', () => {
|
||||
callNext(hook);
|
||||
|
||||
const { state } = hook.result.current;
|
||||
expect(state).toBe('a');
|
||||
});
|
||||
|
||||
it('should always return "a" (calling prev)', () => {
|
||||
callPrev(hook);
|
||||
|
||||
const { state } = hook.result.current;
|
||||
expect(state).toBe('a');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with stateSet updates', () => {
|
||||
const hook = renderHook(({ stateSet }) => useStateList(stateSet), {
|
||||
initialProps: {
|
||||
stateSet: ['a', 'c', 'b', 'f', 'g'],
|
||||
},
|
||||
});
|
||||
|
||||
it('should return the last element after updating with a shorter state set', () => {
|
||||
// Go to the 4th state
|
||||
callNext(hook); // c
|
||||
callNext(hook); // b
|
||||
callNext(hook); // f
|
||||
|
||||
// Update the state set with less elements
|
||||
hook.rerender({
|
||||
stateSet: ['a', 'c'],
|
||||
});
|
||||
|
||||
const { state } = hook.result.current;
|
||||
expect(state).toBe('c');
|
||||
});
|
||||
|
||||
it('should return the element in the same position after updating with a larger state set', () => {
|
||||
hook.rerender({
|
||||
stateSet: ['a', 'f', 'l'],
|
||||
});
|
||||
|
||||
const { state } = hook.result.current;
|
||||
expect(state).toBe('f');
|
||||
});
|
||||
});
|
||||
136
src/__tests__/useTimeout.test.ts
Normal file
136
src/__tests__/useTimeout.test.ts
Normal file
@ -0,0 +1,136 @@
|
||||
import { act, renderHook, RenderHookResult } from '@testing-library/react-hooks';
|
||||
import { useTimeout } from '../index';
|
||||
import { UseTimeoutReturn } from '../useTimeout';
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(useTimeout).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return three functions', () => {
|
||||
const hook = renderHook(() => useTimeout(5));
|
||||
|
||||
expect(hook.result.current.length).toBe(3);
|
||||
expect(typeof hook.result.current[0]).toBe('function');
|
||||
expect(typeof hook.result.current[1]).toBe('function');
|
||||
expect(typeof hook.result.current[2]).toBe('function');
|
||||
});
|
||||
|
||||
function getHook(ms: number = 5): [jest.Mock, RenderHookResult<{ delay: number }, UseTimeoutReturn>] {
|
||||
const spy = jest.fn();
|
||||
return [
|
||||
spy,
|
||||
renderHook(
|
||||
({ delay = 5 }) => {
|
||||
spy();
|
||||
return useTimeout(delay);
|
||||
},
|
||||
{ initialProps: { delay: ms } }
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
it('should re-render component after given amount of time', done => {
|
||||
const [spy, hook] = getHook();
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
hook.waitForNextUpdate().then(() => {
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
done();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
});
|
||||
|
||||
it('should cancel timeout on unmount', () => {
|
||||
const [spy, hook] = getHook();
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
hook.unmount();
|
||||
jest.advanceTimersByTime(5);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('first function should return actual state of timeout', done => {
|
||||
let [, hook] = getHook();
|
||||
let [isReady] = hook.result.current;
|
||||
|
||||
expect(isReady()).toBe(false);
|
||||
hook.unmount();
|
||||
expect(isReady()).toBe(null);
|
||||
|
||||
[, hook] = getHook();
|
||||
[isReady] = hook.result.current;
|
||||
hook.waitForNextUpdate().then(() => {
|
||||
expect(isReady()).toBe(true);
|
||||
|
||||
done();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
});
|
||||
|
||||
it('second function should cancel timeout', () => {
|
||||
const [spy, hook] = getHook();
|
||||
const [isReady, cancel] = hook.result.current;
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(isReady()).toBe(false);
|
||||
|
||||
act(() => {
|
||||
cancel();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(isReady()).toBe(null);
|
||||
});
|
||||
|
||||
it('third function should reset timeout', done => {
|
||||
const [spy, hook] = getHook();
|
||||
const [isReady, cancel, reset] = hook.result.current;
|
||||
|
||||
expect(isReady()).toBe(false);
|
||||
|
||||
act(() => {
|
||||
cancel();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
|
||||
expect(isReady()).toBe(null);
|
||||
|
||||
act(() => {
|
||||
reset();
|
||||
});
|
||||
expect(isReady()).toBe(false);
|
||||
|
||||
hook.waitForNextUpdate().then(() => {
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
expect(isReady()).toBe(true);
|
||||
|
||||
done();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
});
|
||||
|
||||
it('should reset timeout on delay change', done => {
|
||||
const [spy, hook] = getHook(15);
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
hook.rerender({ delay: 5 });
|
||||
|
||||
hook.waitForNextUpdate().then(() => {
|
||||
expect(spy).toHaveBeenCalledTimes(3);
|
||||
|
||||
done();
|
||||
});
|
||||
jest.advanceTimersByTime(15);
|
||||
});
|
||||
116
src/__tests__/useTimeoutFn.test.ts
Normal file
116
src/__tests__/useTimeoutFn.test.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import { act, renderHook, RenderHookResult } from '@testing-library/react-hooks';
|
||||
import { useTimeoutFn } from '../index';
|
||||
import { UseTimeoutFnReturn } from '../useTimeoutFn';
|
||||
|
||||
describe('useTimeoutFn', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(useTimeoutFn).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return three functions', () => {
|
||||
const hook = renderHook(() => useTimeoutFn(() => {}, 5));
|
||||
|
||||
expect(hook.result.current.length).toBe(3);
|
||||
expect(typeof hook.result.current[0]).toBe('function');
|
||||
expect(typeof hook.result.current[1]).toBe('function');
|
||||
expect(typeof hook.result.current[2]).toBe('function');
|
||||
});
|
||||
|
||||
function getHook(ms: number = 5): [jest.Mock, RenderHookResult<{ delay: number }, UseTimeoutFnReturn>] {
|
||||
const spy = jest.fn();
|
||||
return [spy, renderHook(({ delay = 5 }) => useTimeoutFn(spy, delay), { initialProps: { delay: ms } })];
|
||||
}
|
||||
|
||||
it('should call passed function after given amount of time', () => {
|
||||
const [spy] = getHook();
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
jest.advanceTimersByTime(5);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should cancel function call on unmount', () => {
|
||||
const [spy, hook] = getHook();
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
hook.unmount();
|
||||
jest.advanceTimersByTime(5);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('first function should return actual state of timeout', () => {
|
||||
let [, hook] = getHook();
|
||||
let [isReady] = hook.result.current;
|
||||
|
||||
expect(isReady()).toBe(false);
|
||||
hook.unmount();
|
||||
expect(isReady()).toBe(null);
|
||||
|
||||
[, hook] = getHook();
|
||||
[isReady] = hook.result.current;
|
||||
jest.advanceTimersByTime(5);
|
||||
expect(isReady()).toBe(true);
|
||||
});
|
||||
|
||||
it('second function should cancel timeout', () => {
|
||||
const [spy, hook] = getHook();
|
||||
const [isReady, cancel] = hook.result.current;
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
expect(isReady()).toBe(false);
|
||||
|
||||
act(() => {
|
||||
cancel();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
expect(isReady()).toBe(null);
|
||||
});
|
||||
|
||||
it('third function should reset timeout', () => {
|
||||
const [spy, hook] = getHook();
|
||||
const [isReady, cancel, reset] = hook.result.current;
|
||||
|
||||
expect(isReady()).toBe(false);
|
||||
|
||||
act(() => {
|
||||
cancel();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
|
||||
expect(isReady()).toBe(null);
|
||||
|
||||
act(() => {
|
||||
reset();
|
||||
});
|
||||
expect(isReady()).toBe(false);
|
||||
|
||||
jest.advanceTimersByTime(5);
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(isReady()).toBe(true);
|
||||
});
|
||||
|
||||
it('should reset timeout on delay change', () => {
|
||||
const [spy, hook] = getHook(50);
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
hook.rerender({ delay: 5 });
|
||||
|
||||
jest.advanceTimersByTime(5);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
56
src/__tests__/useTween.test.ts
Normal file
56
src/__tests__/useTween.test.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { easing } from 'ts-easing';
|
||||
import * as useRaf from '../useRaf';
|
||||
import useTween from '../useTween';
|
||||
|
||||
let spyUseRaf;
|
||||
let spyEasingInCirc;
|
||||
let spyEasingOutCirc;
|
||||
|
||||
beforeEach(() => {
|
||||
spyUseRaf = jest.spyOn(useRaf, 'default').mockReturnValue(17);
|
||||
spyEasingInCirc = jest.spyOn(easing, 'inCirc').mockReturnValue(999999);
|
||||
spyEasingOutCirc = jest.spyOn(easing, 'outCirc').mockReturnValue(101010);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should init corresponding utils with default values', () => {
|
||||
const { result } = renderHook(() => useTween());
|
||||
|
||||
expect(result.current).toBe(999999);
|
||||
expect(spyEasingInCirc).toHaveBeenCalledTimes(1);
|
||||
expect(spyEasingInCirc).toHaveBeenCalledWith(17);
|
||||
expect(spyUseRaf).toHaveBeenCalledTimes(1);
|
||||
expect(spyUseRaf).toHaveBeenCalledWith(200, 0);
|
||||
});
|
||||
|
||||
it('should init corresponding utils with custom values', () => {
|
||||
const { result } = renderHook(() => useTween('outCirc', 500, 15));
|
||||
|
||||
expect(result.current).toBe(101010);
|
||||
expect(spyEasingOutCirc).toHaveBeenCalledTimes(1);
|
||||
expect(spyEasingOutCirc).toHaveBeenCalledWith(17);
|
||||
expect(spyUseRaf).toHaveBeenCalledTimes(1);
|
||||
expect(spyUseRaf).toHaveBeenCalledWith(500, 15);
|
||||
});
|
||||
|
||||
describe('when invalid easing name is provided', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
jest.spyOn(console, 'trace').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
it('should log an error', () => {
|
||||
const { result } = renderHook(() => useTween('grijanderl'));
|
||||
|
||||
expect(result.current).toBe(0);
|
||||
expect(console.error).toHaveBeenCalledTimes(1);
|
||||
expect(console.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining('useTween() expected "easingName" property to be a valid easing function name')
|
||||
);
|
||||
expect(console.trace).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
26
src/__tests__/useUpdate.test.ts
Normal file
26
src/__tests__/useUpdate.test.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import useUpdate from '../useUpdate';
|
||||
|
||||
it('should init update function', () => {
|
||||
const { result } = renderHook(() => useUpdate());
|
||||
const update = result.current;
|
||||
|
||||
expect(update).toBeInstanceOf(Function);
|
||||
});
|
||||
|
||||
it('should forces a re-render every time update function is called', () => {
|
||||
let renderCount = 0;
|
||||
const { result } = renderHook(() => {
|
||||
renderCount++;
|
||||
return useUpdate();
|
||||
});
|
||||
const update = result.current;
|
||||
|
||||
expect(renderCount).toBe(1);
|
||||
|
||||
act(() => update());
|
||||
expect(renderCount).toBe(2);
|
||||
|
||||
act(() => update());
|
||||
expect(renderCount).toBe(3);
|
||||
});
|
||||
74
src/__tests__/useUpsert.test.ts
Normal file
74
src/__tests__/useUpsert.test.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import useUpsert from '../useUpsert';
|
||||
|
||||
interface TestItem {
|
||||
id: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
const testItems: TestItem[] = [{ id: '1', text: '1' }, { id: '2', text: '2' }];
|
||||
|
||||
const itemsAreEqual = (a: TestItem, b: TestItem) => {
|
||||
return a.id === b.id;
|
||||
};
|
||||
|
||||
const setUp = (initialList: TestItem[] = []) => renderHook(() => useUpsert<TestItem>(itemsAreEqual, initialList));
|
||||
|
||||
describe('useUpsert', () => {
|
||||
describe('initialization', () => {
|
||||
const { result } = setUp(testItems);
|
||||
const [list, utils] = result.current;
|
||||
|
||||
it('properly initiates the list content', () => {
|
||||
expect(list).toEqual(testItems);
|
||||
});
|
||||
|
||||
it('returns an upsert function', () => {
|
||||
expect(utils.upsert).toBeInstanceOf(Function);
|
||||
});
|
||||
});
|
||||
|
||||
describe('upserting a new item', () => {
|
||||
const { result } = setUp(testItems);
|
||||
const [, utils] = result.current;
|
||||
|
||||
const newItem: TestItem = {
|
||||
id: '3',
|
||||
text: '3',
|
||||
};
|
||||
act(() => {
|
||||
utils.upsert(newItem);
|
||||
});
|
||||
|
||||
it('inserts a new item', () => {
|
||||
expect(result.current[0]).toContain(newItem);
|
||||
});
|
||||
it('works immutably', () => {
|
||||
expect(result.current[0]).not.toBe(testItems);
|
||||
});
|
||||
});
|
||||
|
||||
describe('upserting an existing item', () => {
|
||||
const { result } = setUp(testItems);
|
||||
const [, utils] = result.current;
|
||||
|
||||
const newItem: TestItem = {
|
||||
id: '2',
|
||||
text: '4',
|
||||
};
|
||||
act(() => {
|
||||
utils.upsert(newItem);
|
||||
});
|
||||
const updatedList = result.current[0];
|
||||
|
||||
it('has the same length', () => {
|
||||
expect(updatedList).toHaveLength(testItems.length);
|
||||
});
|
||||
it('updates the item', () => {
|
||||
expect(updatedList).toContain(newItem);
|
||||
});
|
||||
it('works immutably', () => {
|
||||
expect(updatedList).not.toBe(testItems);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -23,6 +23,7 @@ export { default as useFullscreen } from './useFullscreen';
|
||||
export { default as useGeolocation } from './useGeolocation';
|
||||
export { default as useGetSet } from './useGetSet';
|
||||
export { default as useGetSetState } from './useGetSetState';
|
||||
export { default as useHarmonicIntervalFn } from './useHarmonicIntervalFn';
|
||||
export { default as useHover } from './useHover';
|
||||
export { default as useHoverDirty } from './useHoverDirty';
|
||||
export { default as useIdle } from './useIdle';
|
||||
@ -54,9 +55,13 @@ export { default as useOrientation } from './useOrientation';
|
||||
export { default as usePageLeave } from './usePageLeave';
|
||||
export { default as usePermission } from './usePermission';
|
||||
export { default as usePrevious } from './usePrevious';
|
||||
export { default as usePreviousDistinct } from './usePreviousDistinct';
|
||||
export { default as usePromise } from './usePromise';
|
||||
export { default as useRaf } from './useRaf';
|
||||
export { default as useRafLoop } from './useRafLoop';
|
||||
/**
|
||||
* @deprecated This hook is obsolete, use `useMountedState` instead
|
||||
*/
|
||||
export { default as useRefMounted } from './useRefMounted';
|
||||
export { default as useScroll } from './useScroll';
|
||||
export { default as useScrolling } from './useScrolling';
|
||||
@ -67,15 +72,18 @@ export { default as useSpeech } from './useSpeech';
|
||||
// not exported because of peer dependency
|
||||
// export { default as useSpring } from './useSpring';
|
||||
export { default as useStartTyping } from './useStartTyping';
|
||||
export { default as useStateList } from './useStateList';
|
||||
export { default as useThrottle } from './useThrottle';
|
||||
export { default as useThrottleFn } from './useThrottleFn';
|
||||
export { default as useTimeout } from './useTimeout';
|
||||
export { default as useTimeoutFn } from './useTimeoutFn';
|
||||
export { default as useTitle } from './useTitle';
|
||||
export { default as useToggle } from './useToggle';
|
||||
export { default as useTween } from './useTween';
|
||||
export { default as useUnmount } from './useUnmount';
|
||||
export { default as useUpdate } from './useUpdate';
|
||||
export { default as useUpdateEffect } from './useUpdateEffect';
|
||||
export { default as useUpsert } from './useUpsert';
|
||||
export { default as useVideo } from './useVideo';
|
||||
export { useWait, Waiter } from './useWait';
|
||||
export { default as useWindowScroll } from './useWindowScroll';
|
||||
|
||||
@ -1,56 +1,85 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import * as React from 'react';
|
||||
import isEqual from 'react-fast-compare';
|
||||
import { off, on } from './util';
|
||||
|
||||
export interface BatterySensorState {
|
||||
const { useState, useEffect } = React;
|
||||
|
||||
export interface BatteryState {
|
||||
charging: boolean;
|
||||
level: number;
|
||||
chargingTime: number;
|
||||
dischargingTime: number;
|
||||
level: number;
|
||||
}
|
||||
|
||||
const useBattery = () => {
|
||||
const [state, setState] = useState({});
|
||||
let mounted = true;
|
||||
let battery: any = null;
|
||||
interface BatteryManager extends Readonly<BatteryState>, EventTarget {
|
||||
onchargingchange: () => void;
|
||||
onchargingtimechange: () => void;
|
||||
ondischargingtimechange: () => void;
|
||||
onlevelchange: () => void;
|
||||
}
|
||||
|
||||
const onChange = () => {
|
||||
const { charging, level, chargingTime, dischargingTime } = battery;
|
||||
setState({
|
||||
charging,
|
||||
level,
|
||||
chargingTime,
|
||||
dischargingTime,
|
||||
});
|
||||
};
|
||||
interface NavigatorWithPossibleBattery extends Navigator {
|
||||
getBattery?: () => Promise<BatteryManager>;
|
||||
}
|
||||
|
||||
const onBattery = () => {
|
||||
onChange();
|
||||
on(battery, 'chargingchange', onChange);
|
||||
on(battery, 'levelchange', onChange);
|
||||
on(battery, 'chargingtimechange', onChange);
|
||||
on(battery, 'dischargingtimechange', onChange);
|
||||
};
|
||||
type UseBatteryState =
|
||||
| { isSupported: false } // Battery API is not supported
|
||||
| { isSupported: true; fetched: false } // battery API supported but not fetched yet
|
||||
| BatteryState & { isSupported: true; fetched: true }; // battery API supported and fetched
|
||||
|
||||
const nav: NavigatorWithPossibleBattery | undefined = typeof navigator === 'object' ? navigator : undefined;
|
||||
const isBatteryApiSupported = nav && typeof nav.getBattery === 'function';
|
||||
|
||||
function useBatteryMock(): UseBatteryState {
|
||||
return { isSupported: false };
|
||||
}
|
||||
|
||||
function useBattery(): UseBatteryState {
|
||||
const [state, setState] = useState<UseBatteryState>({ isSupported: true, fetched: false });
|
||||
|
||||
useEffect(() => {
|
||||
(navigator as any).getBattery().then((bat: any) => {
|
||||
if (mounted) {
|
||||
battery = bat;
|
||||
onBattery();
|
||||
let isMounted = true;
|
||||
let battery: BatteryManager | null = null;
|
||||
|
||||
const handleChange = () => {
|
||||
if (!isMounted || !battery) {
|
||||
return;
|
||||
}
|
||||
const newState: UseBatteryState = {
|
||||
isSupported: true,
|
||||
fetched: true,
|
||||
level: battery.level,
|
||||
charging: battery.charging,
|
||||
dischargingTime: battery.dischargingTime,
|
||||
chargingTime: battery.chargingTime,
|
||||
};
|
||||
!isEqual(state, newState) && setState(newState);
|
||||
};
|
||||
|
||||
nav!.getBattery!().then((bat: BatteryManager) => {
|
||||
if (!isMounted) {
|
||||
return;
|
||||
}
|
||||
battery = bat;
|
||||
on(battery, 'chargingchange', handleChange);
|
||||
on(battery, 'chargingtimechange', handleChange);
|
||||
on(battery, 'dischargingtimechange', handleChange);
|
||||
on(battery, 'levelchange', handleChange);
|
||||
handleChange();
|
||||
});
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
isMounted = false;
|
||||
if (battery) {
|
||||
off(battery, 'chargingchange', onChange);
|
||||
off(battery, 'levelchange', onChange);
|
||||
off(battery, 'chargingtimechange', onChange);
|
||||
off(battery, 'dischargingtimechange', onChange);
|
||||
off(battery, 'chargingchange', handleChange);
|
||||
off(battery, 'chargingtimechange', handleChange);
|
||||
off(battery, 'dischargingtimechange', handleChange);
|
||||
off(battery, 'levelchange', handleChange);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return state;
|
||||
};
|
||||
}
|
||||
|
||||
export default useBattery;
|
||||
export default isBatteryApiSupported ? useBattery : useBatteryMock;
|
||||
|
||||
@ -3,11 +3,13 @@ import { isClient } from './util';
|
||||
|
||||
export interface ListenerType1 {
|
||||
addEventListener(name: string, handler: (event?: any) => void, ...args: any[]);
|
||||
|
||||
removeEventListener(name: string, handler: (event?: any) => void);
|
||||
}
|
||||
|
||||
export interface ListenerType2 {
|
||||
on(name: string, handler: (event?: any) => void, ...args: any[]);
|
||||
|
||||
off(name: string, handler: (event?: any) => void);
|
||||
}
|
||||
|
||||
|
||||
20
src/useHarmonicIntervalFn.ts
Normal file
20
src/useHarmonicIntervalFn.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { setHarmonicInterval, clearHarmonicInterval } from 'set-harmonic-interval';
|
||||
|
||||
const useHarmonicIntervalFn = (fn: Function, delay: number | null = 0) => {
|
||||
const latestCallback = useRef<Function>(() => {});
|
||||
|
||||
useEffect(() => {
|
||||
latestCallback.current = fn;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (delay !== null) {
|
||||
const interval = setHarmonicInterval(() => latestCallback.current(), delay);
|
||||
return () => clearHarmonicInterval(interval);
|
||||
}
|
||||
return undefined;
|
||||
}, [delay]);
|
||||
};
|
||||
|
||||
export default useHarmonicIntervalFn;
|
||||
@ -1,4 +1,4 @@
|
||||
import { useRef, useEffect } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
const useInterval = (callback: Function, delay?: number | null) => {
|
||||
const latestCallback = useRef<Function>(() => {});
|
||||
|
||||
@ -4,6 +4,7 @@ import useEvent, { UseEventTarget } from './useEvent';
|
||||
export type KeyPredicate = (event: KeyboardEvent) => boolean;
|
||||
export type KeyFilter = null | undefined | string | ((event: KeyboardEvent) => boolean);
|
||||
export type Handler = (event: KeyboardEvent) => void;
|
||||
|
||||
export interface UseKeyOptions {
|
||||
event?: 'keydown' | 'keypress' | 'keyup';
|
||||
target?: UseEventTarget;
|
||||
|
||||
@ -1,34 +1,58 @@
|
||||
import { useEffect } from 'react';
|
||||
import { RefObject, useEffect, useRef } from 'react';
|
||||
|
||||
let counter = 0;
|
||||
let originalOverflow: string | null = null;
|
||||
|
||||
const lock = () => {
|
||||
originalOverflow = window.getComputedStyle(document.body).overflow;
|
||||
document.body.style.overflow = 'hidden';
|
||||
};
|
||||
|
||||
const unlock = () => {
|
||||
document.body.style.overflow = originalOverflow;
|
||||
originalOverflow = null;
|
||||
};
|
||||
|
||||
const increment = () => {
|
||||
counter++;
|
||||
if (counter === 1) {
|
||||
lock();
|
||||
export function getClosestBody(el: Element | HTMLElement | HTMLIFrameElement | null): HTMLElement | null {
|
||||
if (!el) {
|
||||
return null;
|
||||
} else if (el.tagName === 'BODY') {
|
||||
return el as HTMLElement;
|
||||
} else if (el.tagName === 'IFRAME') {
|
||||
const document = (el as HTMLIFrameElement).contentDocument;
|
||||
return document ? document.body : null;
|
||||
} else if (!(el as HTMLElement).offsetParent) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const decrement = () => {
|
||||
counter--;
|
||||
if (counter === 0) {
|
||||
unlock();
|
||||
}
|
||||
};
|
||||
return getClosestBody((el as HTMLElement).offsetParent!);
|
||||
}
|
||||
|
||||
const useLockBodyScroll = (enabled: boolean = true) => {
|
||||
useEffect(() => (enabled ? (increment(), decrement) : undefined), [enabled]);
|
||||
};
|
||||
export interface BodyInfoItem {
|
||||
counter: number;
|
||||
initialOverflow: string | null;
|
||||
}
|
||||
|
||||
export default useLockBodyScroll;
|
||||
const bodies: Map<HTMLElement, BodyInfoItem> = new Map();
|
||||
|
||||
const doc: Document | undefined = typeof document === 'object' ? document : undefined;
|
||||
|
||||
export default !doc
|
||||
? function useLockBodyMock(_locked: boolean = true, _elementRef?: RefObject<HTMLElement>) {}
|
||||
: function useLockBody(locked: boolean = true, elementRef?: RefObject<HTMLElement>) {
|
||||
elementRef = elementRef || useRef(doc!.body);
|
||||
|
||||
useEffect(() => {
|
||||
const body = getClosestBody(elementRef!.current);
|
||||
if (!body) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bodyInfo = bodies.get(body);
|
||||
|
||||
if (locked) {
|
||||
if (!bodyInfo) {
|
||||
bodies.set(body, { counter: 1, initialOverflow: body.style.overflow });
|
||||
body.style.overflow = 'hidden';
|
||||
} else {
|
||||
bodies.set(body, { counter: bodyInfo.counter + 1, initialOverflow: bodyInfo.initialOverflow });
|
||||
}
|
||||
} else {
|
||||
if (bodyInfo) {
|
||||
if (bodyInfo.counter === 1) {
|
||||
bodies.delete(body);
|
||||
body.style.overflow = bodyInfo.initialOverflow;
|
||||
} else {
|
||||
bodies.set(body, { counter: bodyInfo.counter - 1, initialOverflow: bodyInfo.initialOverflow });
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [locked, elementRef.current]);
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
export default function useMountedState(): () => boolean {
|
||||
const mountedRef = useRef<boolean>(false);
|
||||
|
||||
19
src/usePreviousDistinct.ts
Normal file
19
src/usePreviousDistinct.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { useRef } from 'react';
|
||||
|
||||
function strictEquals<T>(prev: T | undefined, next: T) {
|
||||
return prev === next;
|
||||
}
|
||||
|
||||
export default function usePreviousDistinct<T>(
|
||||
value: T,
|
||||
compare: (prev: T | undefined, next: T) => boolean = strictEquals
|
||||
) {
|
||||
const prevRef = useRef<T>();
|
||||
const curRef = useRef<T>();
|
||||
if (!compare(curRef.current, value)) {
|
||||
prevRef.current = curRef.current;
|
||||
curRef.current = value;
|
||||
}
|
||||
|
||||
return prevRef.current;
|
||||
}
|
||||
@ -1,5 +1,8 @@
|
||||
import { RefObject, useEffect, useRef } from 'react';
|
||||
|
||||
/**
|
||||
* @deprecated This hook is obsolete, use `useMountedState` instead
|
||||
*/
|
||||
const useRefMounted = (): RefObject<boolean> => {
|
||||
const refMounted = useRef<boolean>(false);
|
||||
|
||||
@ -14,4 +17,7 @@ const useRefMounted = (): RefObject<boolean> => {
|
||||
return refMounted;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated This hook is obsolete, use `useMountedState` instead
|
||||
*/
|
||||
export default useRefMounted;
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
import { useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
const useSetState = <T extends object>(
|
||||
initialState: T = {} as T
|
||||
): [T, (patch: Partial<T> | ((prevState: T) => Partial<T>)) => void] => {
|
||||
const [state, set] = useState<T>(initialState);
|
||||
const setState = patch => {
|
||||
set(prevState => Object.assign({}, prevState, patch instanceof Function ? patch(prevState) : patch));
|
||||
};
|
||||
const setState = useCallback(
|
||||
patch => {
|
||||
set(prevState => Object.assign({}, prevState, patch instanceof Function ? patch(prevState) : patch));
|
||||
},
|
||||
[set]
|
||||
);
|
||||
|
||||
return [state, setState];
|
||||
};
|
||||
|
||||
@ -6,6 +6,7 @@ const { useState, useEffect, useRef } = React;
|
||||
const DRAF = (callback: () => void) => setTimeout(callback, 35);
|
||||
|
||||
export type Element = ((state: State) => React.ReactElement<any>) | React.ReactElement<any>;
|
||||
|
||||
export interface State {
|
||||
width: number;
|
||||
height: number;
|
||||
|
||||
@ -1,31 +1,37 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Spring, SpringSystem } from 'rebound';
|
||||
|
||||
const useSpring = (targetValue: number = 0, tension: number = 50, friction: number = 3) => {
|
||||
const [spring, setSpring] = useState<Spring | null>(null);
|
||||
const [value, setValue] = useState<number>(targetValue);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = {
|
||||
// memoize listener to being able to unsubscribe later properly, otherwise
|
||||
// listener fn will be different on each re-render and wouldn't unsubscribe properly.
|
||||
const listener = useMemo(
|
||||
() => ({
|
||||
onSpringUpdate: currentSpring => {
|
||||
const newValue = currentSpring.getCurrentValue();
|
||||
setValue(newValue);
|
||||
},
|
||||
};
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!spring) {
|
||||
const newSpring = new SpringSystem().createSpring(tension, friction);
|
||||
newSpring.setCurrentValue(targetValue);
|
||||
setSpring(newSpring);
|
||||
newSpring.addListener(listener);
|
||||
return;
|
||||
}
|
||||
|
||||
return () => {
|
||||
spring.removeListener(listener);
|
||||
setSpring(null);
|
||||
if (spring) {
|
||||
spring.removeListener(listener);
|
||||
setSpring(null);
|
||||
}
|
||||
};
|
||||
}, [tension, friction]);
|
||||
}, [tension, friction, spring]);
|
||||
|
||||
useEffect(() => {
|
||||
if (spring) {
|
||||
|
||||
33
src/useStateList.ts
Normal file
33
src/useStateList.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
import useUpdateEffect from './useUpdateEffect';
|
||||
|
||||
export default function useStateList<T>(stateSet: T[] = []): { state: T; next: () => void; prev: () => void } {
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
|
||||
// In case we receive a different state set, check if the current index still exists and
|
||||
// reset it to the last if it don't.
|
||||
useUpdateEffect(() => {
|
||||
if (!stateSet[currentIndex]) {
|
||||
setCurrentIndex(stateSet.length - 1);
|
||||
}
|
||||
}, [stateSet]);
|
||||
|
||||
const next = useCallback(() => {
|
||||
const nextStateIndex = stateSet.length === currentIndex + 1 ? 0 : currentIndex + 1;
|
||||
|
||||
setCurrentIndex(nextStateIndex);
|
||||
}, [stateSet, currentIndex]);
|
||||
|
||||
const prev = useCallback(() => {
|
||||
const prevStateIndex = currentIndex === 0 ? stateSet.length - 1 : currentIndex - 1;
|
||||
|
||||
setCurrentIndex(prevStateIndex);
|
||||
}, [stateSet, currentIndex]);
|
||||
|
||||
return {
|
||||
state: stateSet[currentIndex],
|
||||
next,
|
||||
prev,
|
||||
};
|
||||
}
|
||||
@ -1,19 +1,10 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import useTimeoutFn from './useTimeoutFn';
|
||||
import useUpdate from './useUpdate';
|
||||
|
||||
const useTimeout = (ms: number = 0) => {
|
||||
const [ready, setReady] = useState(false);
|
||||
export type UseTimeoutReturn = [() => boolean | null, () => void, () => void];
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setReady(true);
|
||||
}, ms);
|
||||
export default function useTimeout(ms: number = 0): UseTimeoutReturn {
|
||||
const update = useUpdate();
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [ms]);
|
||||
|
||||
return ready;
|
||||
};
|
||||
|
||||
export default useTimeout;
|
||||
return useTimeoutFn(update, ms);
|
||||
}
|
||||
|
||||
29
src/useTimeoutFn.ts
Normal file
29
src/useTimeoutFn.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
export type UseTimeoutFnReturn = [() => boolean | null, () => void, () => void];
|
||||
|
||||
export default function useTimeoutFn(fn: Function, ms: number = 0): UseTimeoutFnReturn {
|
||||
const ready = useRef<boolean | null>(false);
|
||||
const timeout = useRef<ReturnType<typeof setTimeout>>();
|
||||
|
||||
const isReady = useCallback(() => ready.current, []);
|
||||
const set = useCallback(() => {
|
||||
ready.current = false;
|
||||
timeout.current = setTimeout(() => {
|
||||
ready.current = true;
|
||||
fn();
|
||||
}, ms);
|
||||
}, [ms, fn]);
|
||||
const clear = useCallback(() => {
|
||||
ready.current = null;
|
||||
timeout.current && clearTimeout(timeout.current);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
set();
|
||||
|
||||
return clear;
|
||||
}, [ms]);
|
||||
|
||||
return [isReady, clear, set];
|
||||
}
|
||||
39
src/useUpsert.ts
Normal file
39
src/useUpsert.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import useList, { Actions as ListActions } from './useList';
|
||||
|
||||
export interface Actions<T> extends ListActions<T> {
|
||||
upsert: (item: T) => void;
|
||||
}
|
||||
|
||||
const useUpsert = <T>(
|
||||
comparisonFunction: (upsertedItem: T, existingItem: T) => boolean,
|
||||
initialList: T[] = []
|
||||
): [T[], Actions<T>] => {
|
||||
const [items, actions] = useList(initialList);
|
||||
|
||||
const upsert = (upsertedItem: T) => {
|
||||
let itemWasFound = false;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const existingItem = items[i];
|
||||
|
||||
const shouldUpdate = comparisonFunction(existingItem, upsertedItem);
|
||||
if (shouldUpdate) {
|
||||
actions.updateAt(i, upsertedItem);
|
||||
itemWasFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!itemWasFound) {
|
||||
actions.push(upsertedItem);
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
items,
|
||||
{
|
||||
...actions,
|
||||
upsert,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export default useUpsert;
|
||||
@ -22,6 +22,14 @@
|
||||
"no-empty-interface": [false],
|
||||
"object-literal-sort-keys": false,
|
||||
"no-unused-expression": false,
|
||||
"variable-name": [
|
||||
true,
|
||||
"ban-keywords",
|
||||
"check-format",
|
||||
"allow-pascal-case",
|
||||
"allow-leading-underscore",
|
||||
"allow-trailing-underscore"
|
||||
],
|
||||
"prettier": [
|
||||
true,
|
||||
{
|
||||
|
||||
68
yarn.lock
68
yarn.lock
@ -1948,10 +1948,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-20.0.1.tgz#35cc15b9c4f30a18ef21852e255fdb02f6d59b89"
|
||||
integrity sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==
|
||||
|
||||
"@types/jest@24.0.17":
|
||||
version "24.0.17"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.17.tgz#b66ea026efb746eb5db1356ee28518aaff7af416"
|
||||
integrity sha512-1cy3xkOAfSYn78dsBWy4M3h/QF/HeWPchNFDjysVtp3GHeTdSmtluNnELfCmfNRRHo0OWEcpf+NsEJQvwQfdqQ==
|
||||
"@types/jest@24.0.18":
|
||||
version "24.0.18"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.18.tgz#9c7858d450c59e2164a8a9df0905fc5091944498"
|
||||
integrity sha512-jcDDXdjTcrQzdN06+TSVsPPqxvsZA/5QkYfIZlq1JMw7FdP5AZylbOc+6B/cuDurctRe+MziUMtQ3xQdrbjqyQ==
|
||||
dependencies:
|
||||
"@types/jest-diff" "*"
|
||||
|
||||
@ -7735,10 +7735,10 @@ lines-and-columns@^1.1.6:
|
||||
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
|
||||
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
|
||||
|
||||
lint-staged@9.2.3:
|
||||
version "9.2.3"
|
||||
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-9.2.3.tgz#a4ef2b7033f83e8dbc718556610e20e0098356c0"
|
||||
integrity sha512-ovDmF0c0VJDTP0VmwLetJQ+lVGyNqOkTniwO9S0MOJxGxIExpSRTL56/ZmvXZ1tHNA53GBbXQbfS8RnNGRXFjg==
|
||||
lint-staged@9.2.4:
|
||||
version "9.2.4"
|
||||
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-9.2.4.tgz#7a35839c9c6103ec98eabf43e35657c209509345"
|
||||
integrity sha512-RAt5ogbLRiKy9+T3dKbbPKP6tN2I9VjEtTXQFxdkauUdMjIBsS70ikwwSgsBR8xVVCUSX7sVDyWCyem5xB8YDw==
|
||||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
commander "^2.20.0"
|
||||
@ -9973,6 +9973,11 @@ raf-schd@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.2.tgz#bd44c708188f2e84c810bf55fcea9231bcaed8a0"
|
||||
integrity sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ==
|
||||
|
||||
raf-stub@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/raf-stub/-/raf-stub-3.0.0.tgz#40e53dc3ad3b241311f914bbd41dc11a2c9ee0a9"
|
||||
integrity sha512-64wjDTI8NAkplC3WYF3DUBXmdx8AZF0ubxiicZi83BKW5hcdvMtbwDe6gpFBngTo6+XIJbfwmUP8lMa85UPK6A==
|
||||
|
||||
raf@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575"
|
||||
@ -10135,6 +10140,11 @@ react-focus-lock@^1.18.3:
|
||||
prop-types "^15.6.2"
|
||||
react-clientside-effect "^1.2.0"
|
||||
|
||||
react-frame-component@4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-frame-component/-/react-frame-component-4.1.1.tgz#ea8f7c518ef6b5ad72146dd1f648752369826894"
|
||||
integrity sha512-NfJp90AvYA1R6+uSYafQ+n+UM2HjHqi4WGHeprVXa6quU9d8o6ZFRzQ36uemY82dlkZFzf2jigFx6E4UzNFajA==
|
||||
|
||||
react-helmet-async@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.0.2.tgz#bb55dd8268f7b15aac69c6b22e2f950abda8cc44"
|
||||
@ -10943,10 +10953,10 @@ select@^1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
|
||||
integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=
|
||||
|
||||
semantic-release@15.13.21:
|
||||
version "15.13.21"
|
||||
resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-15.13.21.tgz#d021c75f889cff75ae3410736942bee6c4557da7"
|
||||
integrity sha512-3S9thQas28iv3NeHUqQVsDnxMcBGQICdxabeNnJ8BnbRBvCkgqCg3v9zo/+O5a8GCyxrgjtwJ2iWozL8SiIq1w==
|
||||
semantic-release@15.13.24:
|
||||
version "15.13.24"
|
||||
resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-15.13.24.tgz#f0b9544427d059ba5e3c89ac1545234130796be7"
|
||||
integrity sha512-OPshm6HSp+KmZP9dUv1o3MRILDgOeHYWPI+XSpQRERMri7QkaEiIPkZzoNm2d6KDeFDnp03GphQQS4+Zfo+x/Q==
|
||||
dependencies:
|
||||
"@semantic-release/commit-analyzer" "^6.1.0"
|
||||
"@semantic-release/error" "^2.2.0"
|
||||
@ -10973,7 +10983,7 @@ semantic-release@15.13.21:
|
||||
resolve-from "^5.0.0"
|
||||
semver "^6.0.0"
|
||||
signale "^1.2.1"
|
||||
yargs "^13.1.0"
|
||||
yargs "^14.0.0"
|
||||
|
||||
semver-compare@^1.0.0:
|
||||
version "1.0.0"
|
||||
@ -11057,6 +11067,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
|
||||
|
||||
set-harmonic-interval@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/set-harmonic-interval/-/set-harmonic-interval-1.0.0.tgz#2759658395579fc336b9cd026eefe1ad9e742f9b"
|
||||
integrity sha512-RJtrhB5G10e5A1auBv/jHGq0KWfHH8PAb4ln4+kjiHS46aAP12rSdarSj9GDlxQ3QULA2pefEDpm9Y1Xnz+eng==
|
||||
|
||||
set-value@^0.4.3:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1"
|
||||
@ -12155,10 +12170,10 @@ tslint-react@4.0.0:
|
||||
dependencies:
|
||||
tsutils "^3.9.1"
|
||||
|
||||
tslint@5.18.0:
|
||||
version "5.18.0"
|
||||
resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.18.0.tgz#f61a6ddcf372344ac5e41708095bbf043a147ac6"
|
||||
integrity sha512-Q3kXkuDEijQ37nXZZLKErssQVnwCV/+23gFEMROi8IlbaBG6tXqLPQJ5Wjcyt/yHPKBC+hD5SzuGaMora+ZS6w==
|
||||
tslint@5.19.0:
|
||||
version "5.19.0"
|
||||
resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.19.0.tgz#a2cbd4a7699386da823f6b499b8394d6c47bb968"
|
||||
integrity sha512-1LwwtBxfRJZnUvoS9c0uj8XQtAnyhWr9KlNvDIdB+oXyT+VpsOAaEhEgKi1HrZ8rq0ki/AAnbGSv4KM6/AfVZw==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
builtin-modules "^1.1.1"
|
||||
@ -12881,7 +12896,7 @@ yargs@^11.0.0:
|
||||
y18n "^3.2.1"
|
||||
yargs-parser "^9.0.2"
|
||||
|
||||
yargs@^13.1.0, yargs@^13.3.0:
|
||||
yargs@^13.3.0:
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83"
|
||||
integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==
|
||||
@ -12897,6 +12912,23 @@ yargs@^13.1.0, yargs@^13.3.0:
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^13.1.1"
|
||||
|
||||
yargs@^14.0.0:
|
||||
version "14.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.0.0.tgz#ba4cacc802b3c0b3e36a9e791723763d57a85066"
|
||||
integrity sha512-ssa5JuRjMeZEUjg7bEL99AwpitxU/zWGAGpdj0di41pOEmJti8NR6kyUIJBkR78DTYNPZOU08luUo0GTHuB+ow==
|
||||
dependencies:
|
||||
cliui "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
find-up "^3.0.0"
|
||||
get-caller-file "^2.0.1"
|
||||
require-directory "^2.1.1"
|
||||
require-main-filename "^2.0.0"
|
||||
set-blocking "^2.0.0"
|
||||
string-width "^3.0.0"
|
||||
which-module "^2.0.0"
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^13.1.1"
|
||||
|
||||
yn@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.0.0.tgz#0073c6b56e92aed652fbdfd62431f2d6b9a7a091"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user