@tanem/react-nprogress 7.0.0 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -10
- package/dist/react-nprogress.cjs +8 -3
- package/dist/react-nprogress.cjs.map +1 -1
- package/dist/react-nprogress.d.cts +2 -1
- package/dist/react-nprogress.d.cts.map +1 -1
- package/dist/react-nprogress.d.mts +2 -1
- package/dist/react-nprogress.d.mts.map +1 -1
- package/dist/react-nprogress.mjs +9 -4
- package/dist/react-nprogress.mjs.map +1 -1
- package/package.json +1 -1
- package/src/types.ts +1 -0
- package/src/useNProgress.tsx +31 -15
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
This is a React port of [rstacruz](https://github.com/rstacruz)'s [`nprogress`](https://github.com/rstacruz/nprogress) module. It exposes an API that encapsulates the logic of `nprogress` and renders nothing, allowing consumers to implement their own rendering.
|
|
16
16
|
|
|
17
|
+
Two versions of `nprogress` are in circulation and they trickle differently. The 2014 npm release, `0.2.0`, adds a random amount of at most `0.02` every 800ms. The repository's master branch, never published to npm, adds tiered amounts every 200ms. This library mirrors master, so a side by side comparison against the npm package or the official demo page will show a different pace. The [`increment`](#increment) option covers the `0.2.0` pacing if you prefer the older feel.
|
|
18
|
+
|
|
17
19
|
## When to Use This
|
|
18
20
|
|
|
19
21
|
This package is a headless primitive. It renders no markup and ships no CSS, supplying only the pacing state: a `progress` value that trickles towards completion, an `isFinished` flag, and the `animationDuration` to transition with. The bar itself is yours to write.
|
|
@@ -70,6 +72,24 @@ const Progress = ({ isAnimating }) => (
|
|
|
70
72
|
)
|
|
71
73
|
```
|
|
72
74
|
|
|
75
|
+
**Restarting**
|
|
76
|
+
|
|
77
|
+
Both patterns leave the bar mounted between runs, and `progress` returns to `minimum` when it starts again. A bar that transitions `margin-left` or `transform` therefore animates backwards from where it finished, in full view, before it starts trickling forward. Back to back navigations hit this every time.
|
|
78
|
+
|
|
79
|
+
Change a `key` on the bar whenever it starts, so React mounts a fresh element at `minimum` instead:
|
|
80
|
+
|
|
81
|
+
```jsx
|
|
82
|
+
const [state, setState] = useState({ isAnimating: false, key: 0 })
|
|
83
|
+
|
|
84
|
+
const start = () => {
|
|
85
|
+
setState((prevState) => ({ isAnimating: true, key: prevState.key ^ 1 }))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return <Progress isAnimating={state.isAnimating} key={state.key} />
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Every entry in [Live Examples](#live-examples) does this. Dropping the transition while `isFinished` is not an alternative: `isFinished` is already `false` by the time `progress` resets, so the transition is back on for the step that moves the bar.
|
|
92
|
+
|
|
73
93
|
## API
|
|
74
94
|
|
|
75
95
|
The package exports one hook and one component. Both take the same [options](#options) and produce the same [values](#return-value), so the choice between them is a matter of which pattern suits the calling code. Both shapes are exported as types, for typing code that wraps either entry point:
|
|
@@ -85,6 +105,7 @@ Returns the state of one progress bar. Call it once per bar: two calls, or two m
|
|
|
85
105
|
```jsx
|
|
86
106
|
const { animationDuration, isFinished, progress } = useNProgress({
|
|
87
107
|
animationDuration: 300,
|
|
108
|
+
increment: (progress) => progress + 0.01,
|
|
88
109
|
incrementDuration: 500,
|
|
89
110
|
isAnimating: true,
|
|
90
111
|
minimum: 0.1,
|
|
@@ -98,6 +119,7 @@ Takes the options as props and calls `children` with the values the hook returns
|
|
|
98
119
|
```jsx
|
|
99
120
|
<NProgress
|
|
100
121
|
animationDuration={300}
|
|
122
|
+
increment={(progress) => progress + 0.01}
|
|
101
123
|
incrementDuration={500}
|
|
102
124
|
isAnimating
|
|
103
125
|
minimum={0.1}
|
|
@@ -110,22 +132,43 @@ Takes the options as props and calls `children` with the values the hook returns
|
|
|
110
132
|
|
|
111
133
|
### Options
|
|
112
134
|
|
|
113
|
-
All
|
|
135
|
+
All five options are optional. The type is `NProgressOptions`.
|
|
114
136
|
|
|
115
|
-
| Option | Type
|
|
116
|
-
| ----------------------------------------- |
|
|
117
|
-
| [`animationDuration`](#animationduration) | `number`
|
|
118
|
-
| [`
|
|
119
|
-
| [`
|
|
120
|
-
| [`
|
|
137
|
+
| Option | Type | Default |
|
|
138
|
+
| ----------------------------------------- | ------------------------------ | -------------- |
|
|
139
|
+
| [`animationDuration`](#animationduration) | `number` | `200` |
|
|
140
|
+
| [`increment`](#increment) | `(progress: number) => number` | tiered trickle |
|
|
141
|
+
| [`incrementDuration`](#incrementduration) | `number` | `200` |
|
|
142
|
+
| [`isAnimating`](#isanimating) | `boolean` | `false` |
|
|
143
|
+
| [`minimum`](#minimum) | `number` | `0.08` |
|
|
121
144
|
|
|
122
145
|
#### `animationDuration`
|
|
123
146
|
|
|
124
147
|
Milliseconds the bar is given to animate out once it completes. `progress` reaches `1` as soon as `isAnimating` goes `false`, and `isFinished` follows this many milliseconds later, leaving that window for the exit transition. The value is also returned unchanged, so a single number drives both the timing and the CSS transitions.
|
|
125
148
|
|
|
149
|
+
#### `increment`
|
|
150
|
+
|
|
151
|
+
Size of each trickle step. The function is called with the current `progress` and returns the next value. The default is the tiered curve nprogress uses: `+0.1` below `0.2`, then `+0.04`, `+0.02`, and `+0.005` as `progress` grows, held at a ceiling of `0.994` so the bar never looks complete before it is.
|
|
152
|
+
|
|
153
|
+
The return value is clamped to between `minimum` and `1`, and nothing else. A custom function therefore owns its own ceiling. Leave it short of `1`, since reaching `1` is what completion means, and let `isAnimating` going `false` take the bar the rest of the way.
|
|
154
|
+
|
|
155
|
+
Returning a random amount is fine, but keep the function free of other side effects. It runs inside a React state update, and StrictMode calls it twice per increment in development. This trickles a random amount of at most `0.02` every 800ms, which is how nprogress `0.2.0` paces itself:
|
|
156
|
+
|
|
157
|
+
```jsx
|
|
158
|
+
const { progress } = useNProgress({
|
|
159
|
+
increment: (progress) => Math.min(progress + Math.random() * 0.02, 0.994),
|
|
160
|
+
incrementDuration: 800,
|
|
161
|
+
isAnimating,
|
|
162
|
+
})
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`0.2.0` also transitions the bar with `ease` where master uses `linear`. Easing lives in your renderer's CSS, so match it there if you want the rest of that look. The [Classic 0.2.0](https://github.com/tanem/react-nprogress/tree/master/examples/classic-020) example puts both together.
|
|
166
|
+
|
|
167
|
+
A new function identity on every render is fine too: passing an inline function does not restart the trickle timer. The next increment uses the latest function.
|
|
168
|
+
|
|
126
169
|
#### `incrementDuration`
|
|
127
170
|
|
|
128
|
-
Milliseconds between increments while the bar is animating. It controls the trickle pacing only
|
|
171
|
+
Milliseconds between increments while the bar is animating. It controls the trickle pacing only. Step size is [`increment`](#increment).
|
|
129
172
|
|
|
130
173
|
#### `isAnimating`
|
|
131
174
|
|
|
@@ -133,7 +176,7 @@ Whether the bar is running. Going `true` starts it, going `false` completes it.
|
|
|
133
176
|
|
|
134
177
|
#### `minimum`
|
|
135
178
|
|
|
136
|
-
Lower bound for `progress`, between `0` and `1`. The first
|
|
179
|
+
Lower bound for `progress`, between `0` and `1`. The bar first appears at this value, then trickles up from there. Changing it while the bar is animating does not rewind the bar. Progress holds where it is, and the new bound applies from the next increment.
|
|
137
180
|
|
|
138
181
|
### Return Value
|
|
139
182
|
|
|
@@ -143,12 +186,13 @@ Lower bound for `progress`, between `0` and `1`. The first increment starts from
|
|
|
143
186
|
| ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
144
187
|
| `animationDuration` | `number` | The `animationDuration` option, passed through so rendering code can transition with it. |
|
|
145
188
|
| `isFinished` | `boolean` | `true` before the bar starts and again once it has animated out. `false` from when `isAnimating` goes `true` until `animationDuration` after it goes `false`. |
|
|
146
|
-
| `progress` | `number` | Starts at `0
|
|
189
|
+
| `progress` | `number` | Starts at `0`, appears at `minimum` when the bar starts, then trickles up by [`increment`](#increment) and goes to `1` on completion. |
|
|
147
190
|
|
|
148
191
|
## Live Examples
|
|
149
192
|
|
|
150
193
|
| Example | Sandbox |
|
|
151
194
|
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
|
|
195
|
+
| [Classic 0.2.0](https://github.com/tanem/react-nprogress/tree/master/examples/classic-020) | [Open](https://codesandbox.io/p/devbox/github/tanem/react-nprogress/tree/master/examples/classic-020) |
|
|
152
196
|
| [Material UI](https://github.com/tanem/react-nprogress/tree/master/examples/material-ui) | [Open](https://codesandbox.io/p/devbox/github/tanem/react-nprogress/tree/master/examples/material-ui) |
|
|
153
197
|
| [Multiple Instances](https://github.com/tanem/react-nprogress/tree/master/examples/multiple-instances) | [Open](https://codesandbox.io/p/devbox/github/tanem/react-nprogress/tree/master/examples/multiple-instances) |
|
|
154
198
|
| [Next App Router](https://github.com/tanem/react-nprogress/tree/master/examples/next-app-router) | [Open](https://codesandbox.io/p/devbox/github/tanem/react-nprogress/tree/master/examples/next-app-router) |
|
package/dist/react-nprogress.cjs
CHANGED
|
@@ -62,16 +62,20 @@ const reducer = (state, action) => {
|
|
|
62
62
|
};
|
|
63
63
|
case "start": return state.phase === "animating" ? state : {
|
|
64
64
|
phase: "animating",
|
|
65
|
-
progress: clamp(
|
|
65
|
+
progress: clamp(0, action.minimum, 1)
|
|
66
66
|
};
|
|
67
67
|
case "trickle": return {
|
|
68
68
|
...state,
|
|
69
|
-
progress: clamp(increment(state.progress), action.minimum, 1)
|
|
69
|
+
progress: clamp(action.increment(state.progress), action.minimum, 1)
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
};
|
|
73
|
-
const useNProgress = ({ animationDuration = 200, incrementDuration = 200, isAnimating = false, minimum = .08 } = {}) => {
|
|
73
|
+
const useNProgress = ({ animationDuration = 200, increment: increment$1 = increment, incrementDuration = 200, isAnimating = false, minimum = .08 } = {}) => {
|
|
74
74
|
const [{ phase, progress }, dispatch] = (0, react.useReducer)(reducer, initialState);
|
|
75
|
+
const incrementRef = (0, react.useRef)(increment$1);
|
|
76
|
+
(0, react.useEffect)(() => {
|
|
77
|
+
incrementRef.current = increment$1;
|
|
78
|
+
});
|
|
75
79
|
(0, react.useEffect)(() => {
|
|
76
80
|
dispatch(isAnimating ? {
|
|
77
81
|
minimum,
|
|
@@ -83,6 +87,7 @@ const useNProgress = ({ animationDuration = 200, incrementDuration = 200, isAnim
|
|
|
83
87
|
const timeout = createTimeout();
|
|
84
88
|
const trickle = () => {
|
|
85
89
|
dispatch({
|
|
90
|
+
increment: incrementRef.current,
|
|
86
91
|
minimum,
|
|
87
92
|
type: "trickle"
|
|
88
93
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-nprogress.cjs","names":["useReducer"],"sources":["../src/clamp.ts","../src/createTimeout.ts","../src/increment.ts","../src/useNProgress.tsx","../src/NProgress.tsx"],"sourcesContent":["export const clamp = (num: number, lower: number, upper: number): number => {\n num = num <= upper ? num : upper\n num = num >= lower ? num : lower\n return num\n}\n","// Uses requestAnimationFrame rather than setTimeout for smoother animation\n// timing. Note that rAF is throttled or paused in background tabs, so progress\n// will stall when the tab is hidden and resume when it regains focus.\nexport const createTimeout = () => {\n let handle: number | undefined\n\n const cancel = (): void => {\n if (handle !== undefined) {\n window.cancelAnimationFrame(handle)\n }\n }\n\n const schedule = (callback: () => void, delay: number): void => {\n cancel()\n\n let deltaTime\n let start: number | undefined\n const frame: FrameRequestCallback = (time) => {\n start = start || time\n deltaTime = time - start\n if (deltaTime > delay) {\n callback()\n return\n }\n handle = window.requestAnimationFrame(frame)\n }\n\n handle = window.requestAnimationFrame(frame)\n }\n\n return {\n cancel,\n schedule,\n }\n}\n","import { clamp } from './clamp'\n\nexport const increment = (progress: number): number => {\n let amount = 0\n\n if (progress >= 0 && progress < 0.2) {\n amount = 0.1\n } else if (progress >= 0.2 && progress < 0.5) {\n amount = 0.04\n } else if (progress >= 0.5 && progress < 0.8) {\n amount = 0.02\n } else if (progress >= 0.8 && progress < 0.99) {\n amount = 0.005\n }\n\n return clamp(progress + amount, 0, 0.994)\n}\n","import { useEffect, useReducer } from 'react'\n\nimport { clamp } from './clamp'\nimport { createTimeout } from './createTimeout'\nimport { increment } from './increment'\nimport type { NProgressOptions, NProgressState } from './types'\n\n// A four-phase state machine. `idle` and `finished` both report\n// `isFinished: true` and differ only in the progress they hold, so the phase,\n// not `isFinished`, is what decides which transitions and timers apply.\ntype Phase = 'animating' | 'completing' | 'finished' | 'idle'\n\ninterface State {\n phase: Phase\n progress: number\n}\n\ntype Action =\n | { minimum: number
|
|
1
|
+
{"version":3,"file":"react-nprogress.cjs","names":["defaultIncrement","useReducer","useRef","increment"],"sources":["../src/clamp.ts","../src/createTimeout.ts","../src/increment.ts","../src/useNProgress.tsx","../src/NProgress.tsx"],"sourcesContent":["export const clamp = (num: number, lower: number, upper: number): number => {\n num = num <= upper ? num : upper\n num = num >= lower ? num : lower\n return num\n}\n","// Uses requestAnimationFrame rather than setTimeout for smoother animation\n// timing. Note that rAF is throttled or paused in background tabs, so progress\n// will stall when the tab is hidden and resume when it regains focus.\nexport const createTimeout = () => {\n let handle: number | undefined\n\n const cancel = (): void => {\n if (handle !== undefined) {\n window.cancelAnimationFrame(handle)\n }\n }\n\n const schedule = (callback: () => void, delay: number): void => {\n cancel()\n\n let deltaTime\n let start: number | undefined\n const frame: FrameRequestCallback = (time) => {\n start = start || time\n deltaTime = time - start\n if (deltaTime > delay) {\n callback()\n return\n }\n handle = window.requestAnimationFrame(frame)\n }\n\n handle = window.requestAnimationFrame(frame)\n }\n\n return {\n cancel,\n schedule,\n }\n}\n","import { clamp } from './clamp'\n\nexport const increment = (progress: number): number => {\n let amount = 0\n\n if (progress >= 0 && progress < 0.2) {\n amount = 0.1\n } else if (progress >= 0.2 && progress < 0.5) {\n amount = 0.04\n } else if (progress >= 0.5 && progress < 0.8) {\n amount = 0.02\n } else if (progress >= 0.8 && progress < 0.99) {\n amount = 0.005\n }\n\n return clamp(progress + amount, 0, 0.994)\n}\n","import { useEffect, useReducer, useRef } from 'react'\n\nimport { clamp } from './clamp'\nimport { createTimeout } from './createTimeout'\nimport { increment as defaultIncrement } from './increment'\nimport type { NProgressOptions, NProgressState } from './types'\n\n// A four-phase state machine. `idle` and `finished` both report\n// `isFinished: true` and differ only in the progress they hold, so the phase,\n// not `isFinished`, is what decides which transitions and timers apply.\ntype Phase = 'animating' | 'completing' | 'finished' | 'idle'\n\ninterface State {\n phase: Phase\n progress: number\n}\n\ntype Action =\n | {\n increment: (progress: number) => number\n minimum: number\n type: 'trickle'\n }\n | { minimum: number; type: 'start' }\n | { type: 'complete' }\n | { type: 'finish' }\n\nconst initialState: State = {\n phase: 'idle',\n progress: 0,\n}\n\nconst reducer = (state: State, action: Action): State => {\n switch (action.type) {\n case 'complete':\n // The original nprogress `done()` computes a random progress jump before\n // animating to 1, but its queue runs both steps in the same tick, so the\n // jump's CSS is overwritten before paint and never renders. Omitting the\n // jump changes nothing visually.\n //\n // Ignored unless an animation is actually running, which is what makes a\n // StrictMode double-mount a no-op rather than a spurious completion.\n return state.phase === 'animating'\n ? { phase: 'completing', progress: 1 }\n : state\n\n case 'finish':\n return { phase: 'finished', progress: 1 }\n\n case 'start':\n // Matches the original nprogress `start()`, which calls set(0) and so\n // paints first at `minimum` before any trickle runs.\n //\n // Guarded the same way as `complete`, and for the same reason: a repeat\n // dispatch against a running animation must not rewind the bar. That\n // happens whenever `minimum` changes mid-animation, as well as on a\n // StrictMode double-mount.\n return state.phase === 'animating'\n ? state\n : {\n phase: 'animating',\n progress: clamp(0, action.minimum, 1),\n }\n\n case 'trickle':\n // Clamped here rather than trusted from the increment function, so a\n // custom one cannot take the bar outside the documented range. Stopping\n // short of 1 is that function's own business: 1 is what completion\n // means.\n return {\n ...state,\n progress: clamp(action.increment(state.progress), action.minimum, 1),\n }\n }\n}\n\nexport const useNProgress = ({\n animationDuration = 200,\n increment = defaultIncrement,\n incrementDuration = 200,\n isAnimating = false,\n minimum = 0.08,\n}: NProgressOptions = {}): NProgressState => {\n const [{ phase, progress }, dispatch] = useReducer(reducer, initialState)\n\n // Held in a ref so the trickle timer can read the latest increment function\n // without listing it as a dependency. Consumers commonly pass an inline\n // function, whose identity changes every render; depending on it directly\n // would cancel and recreate the timer each time, and a render loop faster\n // than `incrementDuration` would stop the bar advancing altogether.\n const incrementRef = useRef(increment)\n useEffect(() => {\n incrementRef.current = increment\n })\n\n useEffect(() => {\n dispatch(isAnimating ? { minimum, type: 'start' } : { type: 'complete' })\n }, [isAnimating, minimum])\n\n // A timer per running phase, rather than one effect branching over both.\n // Each then depends only on the options its own phase reads, so changing an\n // option the running phase ignores cannot cancel its timer. Both are keyed\n // on the phase rather than on progress, so trickling does not tear down and\n // recreate the timer mid-animation.\n useEffect(() => {\n if (phase !== 'animating') {\n return\n }\n\n const timeout = createTimeout()\n\n const trickle = () => {\n dispatch({ increment: incrementRef.current, minimum, type: 'trickle' })\n timeout.schedule(trickle, incrementDuration)\n }\n timeout.schedule(trickle, incrementDuration)\n\n return () => timeout.cancel()\n }, [incrementDuration, minimum, phase])\n\n useEffect(() => {\n if (phase !== 'completing') {\n return\n }\n\n const timeout = createTimeout()\n timeout.schedule(() => dispatch({ type: 'finish' }), animationDuration)\n\n return () => timeout.cancel()\n }, [animationDuration, phase])\n\n return {\n animationDuration,\n isFinished: phase === 'finished' || phase === 'idle',\n progress,\n }\n}\n","import type { FC, ReactElement } from 'react'\n\nimport type { NProgressOptions, NProgressState } from './types'\nimport { useNProgress } from './useNProgress'\n\ntype Props = NProgressOptions & {\n children: (renderProps: NProgressState) => ReactElement\n}\n\nexport const NProgress: FC<Props> = ({ children, ...restProps }: Props) => {\n const renderProps = useNProgress(restProps)\n return children(renderProps)\n}\n"],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import { FC, ReactElement } from "react";
|
|
|
2
2
|
//#region src/types.d.ts
|
|
3
3
|
interface NProgressOptions {
|
|
4
4
|
animationDuration?: number;
|
|
5
|
+
increment?: (progress: number) => number;
|
|
5
6
|
incrementDuration?: number;
|
|
6
7
|
isAnimating?: boolean;
|
|
7
8
|
minimum?: number;
|
|
@@ -19,7 +20,7 @@ type Props = NProgressOptions & {
|
|
|
19
20
|
declare const NProgress: FC<Props>;
|
|
20
21
|
//#endregion
|
|
21
22
|
//#region src/useNProgress.d.ts
|
|
22
|
-
declare const useNProgress: ({ animationDuration, incrementDuration, isAnimating, minimum }?: NProgressOptions) => NProgressState;
|
|
23
|
+
declare const useNProgress: ({ animationDuration, increment, incrementDuration, isAnimating, minimum }?: NProgressOptions) => NProgressState;
|
|
23
24
|
//#endregion
|
|
24
25
|
export { NProgress, type NProgressOptions, type NProgressState, useNProgress };
|
|
25
26
|
//# sourceMappingURL=react-nprogress.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-nprogress.d.cts","names":[],"sources":["../src/types.ts","../src/NProgress.tsx","../src/useNProgress.tsx"],"mappings":";;UAAiB;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;;;;
|
|
1
|
+
{"version":3,"file":"react-nprogress.d.cts","names":[],"sources":["../src/types.ts","../src/NProgress.tsx","../src/useNProgress.tsx"],"mappings":";;UAAiB;EACf;EACA,aAAa;EACb;EACA;EACA;;UAGe;EACf;EACA;EACA;;;;KCNG,QAAQ;EACX,WAAW,aAAa,mBAAmB;;cAGhC,WAAW,GAAG;;;cCmEd,iBAAgB,mBAAA,WAAA,mBAAA,aAAA,YAM1B,qBAAwB"}
|
|
@@ -2,6 +2,7 @@ import { FC, ReactElement } from "react";
|
|
|
2
2
|
//#region src/types.d.ts
|
|
3
3
|
interface NProgressOptions {
|
|
4
4
|
animationDuration?: number;
|
|
5
|
+
increment?: (progress: number) => number;
|
|
5
6
|
incrementDuration?: number;
|
|
6
7
|
isAnimating?: boolean;
|
|
7
8
|
minimum?: number;
|
|
@@ -19,7 +20,7 @@ type Props = NProgressOptions & {
|
|
|
19
20
|
declare const NProgress: FC<Props>;
|
|
20
21
|
//#endregion
|
|
21
22
|
//#region src/useNProgress.d.ts
|
|
22
|
-
declare const useNProgress: ({ animationDuration, incrementDuration, isAnimating, minimum }?: NProgressOptions) => NProgressState;
|
|
23
|
+
declare const useNProgress: ({ animationDuration, increment, incrementDuration, isAnimating, minimum }?: NProgressOptions) => NProgressState;
|
|
23
24
|
//#endregion
|
|
24
25
|
export { NProgress, type NProgressOptions, type NProgressState, useNProgress };
|
|
25
26
|
//# sourceMappingURL=react-nprogress.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-nprogress.d.mts","names":[],"sources":["../src/types.ts","../src/NProgress.tsx","../src/useNProgress.tsx"],"mappings":";;UAAiB;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;;;;
|
|
1
|
+
{"version":3,"file":"react-nprogress.d.mts","names":[],"sources":["../src/types.ts","../src/NProgress.tsx","../src/useNProgress.tsx"],"mappings":";;UAAiB;EACf;EACA,aAAa;EACb;EACA;EACA;;UAGe;EACf;EACA;EACA;;;;KCNG,QAAQ;EACX,WAAW,aAAa,mBAAmB;;cAGhC,WAAW,GAAG;;;cCmEd,iBAAgB,mBAAA,WAAA,mBAAA,aAAA,YAM1B,qBAAwB"}
|
package/dist/react-nprogress.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useEffect, useReducer } from "react";
|
|
2
|
+
import { useEffect, useReducer, useRef } from "react";
|
|
3
3
|
//#region src/clamp.ts
|
|
4
4
|
const clamp = (num, lower, upper) => {
|
|
5
5
|
num = num <= upper ? num : upper;
|
|
@@ -61,16 +61,20 @@ const reducer = (state, action) => {
|
|
|
61
61
|
};
|
|
62
62
|
case "start": return state.phase === "animating" ? state : {
|
|
63
63
|
phase: "animating",
|
|
64
|
-
progress: clamp(
|
|
64
|
+
progress: clamp(0, action.minimum, 1)
|
|
65
65
|
};
|
|
66
66
|
case "trickle": return {
|
|
67
67
|
...state,
|
|
68
|
-
progress: clamp(increment(state.progress), action.minimum, 1)
|
|
68
|
+
progress: clamp(action.increment(state.progress), action.minimum, 1)
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
};
|
|
72
|
-
const useNProgress = ({ animationDuration = 200, incrementDuration = 200, isAnimating = false, minimum = .08 } = {}) => {
|
|
72
|
+
const useNProgress = ({ animationDuration = 200, increment: increment$1 = increment, incrementDuration = 200, isAnimating = false, minimum = .08 } = {}) => {
|
|
73
73
|
const [{ phase, progress }, dispatch] = useReducer(reducer, initialState);
|
|
74
|
+
const incrementRef = useRef(increment$1);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
incrementRef.current = increment$1;
|
|
77
|
+
});
|
|
74
78
|
useEffect(() => {
|
|
75
79
|
dispatch(isAnimating ? {
|
|
76
80
|
minimum,
|
|
@@ -82,6 +86,7 @@ const useNProgress = ({ animationDuration = 200, incrementDuration = 200, isAnim
|
|
|
82
86
|
const timeout = createTimeout();
|
|
83
87
|
const trickle = () => {
|
|
84
88
|
dispatch({
|
|
89
|
+
increment: incrementRef.current,
|
|
85
90
|
minimum,
|
|
86
91
|
type: "trickle"
|
|
87
92
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-nprogress.mjs","names":[],"sources":["../src/clamp.ts","../src/createTimeout.ts","../src/increment.ts","../src/useNProgress.tsx","../src/NProgress.tsx"],"sourcesContent":["export const clamp = (num: number, lower: number, upper: number): number => {\n num = num <= upper ? num : upper\n num = num >= lower ? num : lower\n return num\n}\n","// Uses requestAnimationFrame rather than setTimeout for smoother animation\n// timing. Note that rAF is throttled or paused in background tabs, so progress\n// will stall when the tab is hidden and resume when it regains focus.\nexport const createTimeout = () => {\n let handle: number | undefined\n\n const cancel = (): void => {\n if (handle !== undefined) {\n window.cancelAnimationFrame(handle)\n }\n }\n\n const schedule = (callback: () => void, delay: number): void => {\n cancel()\n\n let deltaTime\n let start: number | undefined\n const frame: FrameRequestCallback = (time) => {\n start = start || time\n deltaTime = time - start\n if (deltaTime > delay) {\n callback()\n return\n }\n handle = window.requestAnimationFrame(frame)\n }\n\n handle = window.requestAnimationFrame(frame)\n }\n\n return {\n cancel,\n schedule,\n }\n}\n","import { clamp } from './clamp'\n\nexport const increment = (progress: number): number => {\n let amount = 0\n\n if (progress >= 0 && progress < 0.2) {\n amount = 0.1\n } else if (progress >= 0.2 && progress < 0.5) {\n amount = 0.04\n } else if (progress >= 0.5 && progress < 0.8) {\n amount = 0.02\n } else if (progress >= 0.8 && progress < 0.99) {\n amount = 0.005\n }\n\n return clamp(progress + amount, 0, 0.994)\n}\n","import { useEffect, useReducer } from 'react'\n\nimport { clamp } from './clamp'\nimport { createTimeout } from './createTimeout'\nimport { increment } from './increment'\nimport type { NProgressOptions, NProgressState } from './types'\n\n// A four-phase state machine. `idle` and `finished` both report\n// `isFinished: true` and differ only in the progress they hold, so the phase,\n// not `isFinished`, is what decides which transitions and timers apply.\ntype Phase = 'animating' | 'completing' | 'finished' | 'idle'\n\ninterface State {\n phase: Phase\n progress: number\n}\n\ntype Action =\n | { minimum: number
|
|
1
|
+
{"version":3,"file":"react-nprogress.mjs","names":["defaultIncrement","increment"],"sources":["../src/clamp.ts","../src/createTimeout.ts","../src/increment.ts","../src/useNProgress.tsx","../src/NProgress.tsx"],"sourcesContent":["export const clamp = (num: number, lower: number, upper: number): number => {\n num = num <= upper ? num : upper\n num = num >= lower ? num : lower\n return num\n}\n","// Uses requestAnimationFrame rather than setTimeout for smoother animation\n// timing. Note that rAF is throttled or paused in background tabs, so progress\n// will stall when the tab is hidden and resume when it regains focus.\nexport const createTimeout = () => {\n let handle: number | undefined\n\n const cancel = (): void => {\n if (handle !== undefined) {\n window.cancelAnimationFrame(handle)\n }\n }\n\n const schedule = (callback: () => void, delay: number): void => {\n cancel()\n\n let deltaTime\n let start: number | undefined\n const frame: FrameRequestCallback = (time) => {\n start = start || time\n deltaTime = time - start\n if (deltaTime > delay) {\n callback()\n return\n }\n handle = window.requestAnimationFrame(frame)\n }\n\n handle = window.requestAnimationFrame(frame)\n }\n\n return {\n cancel,\n schedule,\n }\n}\n","import { clamp } from './clamp'\n\nexport const increment = (progress: number): number => {\n let amount = 0\n\n if (progress >= 0 && progress < 0.2) {\n amount = 0.1\n } else if (progress >= 0.2 && progress < 0.5) {\n amount = 0.04\n } else if (progress >= 0.5 && progress < 0.8) {\n amount = 0.02\n } else if (progress >= 0.8 && progress < 0.99) {\n amount = 0.005\n }\n\n return clamp(progress + amount, 0, 0.994)\n}\n","import { useEffect, useReducer, useRef } from 'react'\n\nimport { clamp } from './clamp'\nimport { createTimeout } from './createTimeout'\nimport { increment as defaultIncrement } from './increment'\nimport type { NProgressOptions, NProgressState } from './types'\n\n// A four-phase state machine. `idle` and `finished` both report\n// `isFinished: true` and differ only in the progress they hold, so the phase,\n// not `isFinished`, is what decides which transitions and timers apply.\ntype Phase = 'animating' | 'completing' | 'finished' | 'idle'\n\ninterface State {\n phase: Phase\n progress: number\n}\n\ntype Action =\n | {\n increment: (progress: number) => number\n minimum: number\n type: 'trickle'\n }\n | { minimum: number; type: 'start' }\n | { type: 'complete' }\n | { type: 'finish' }\n\nconst initialState: State = {\n phase: 'idle',\n progress: 0,\n}\n\nconst reducer = (state: State, action: Action): State => {\n switch (action.type) {\n case 'complete':\n // The original nprogress `done()` computes a random progress jump before\n // animating to 1, but its queue runs both steps in the same tick, so the\n // jump's CSS is overwritten before paint and never renders. Omitting the\n // jump changes nothing visually.\n //\n // Ignored unless an animation is actually running, which is what makes a\n // StrictMode double-mount a no-op rather than a spurious completion.\n return state.phase === 'animating'\n ? { phase: 'completing', progress: 1 }\n : state\n\n case 'finish':\n return { phase: 'finished', progress: 1 }\n\n case 'start':\n // Matches the original nprogress `start()`, which calls set(0) and so\n // paints first at `minimum` before any trickle runs.\n //\n // Guarded the same way as `complete`, and for the same reason: a repeat\n // dispatch against a running animation must not rewind the bar. That\n // happens whenever `minimum` changes mid-animation, as well as on a\n // StrictMode double-mount.\n return state.phase === 'animating'\n ? state\n : {\n phase: 'animating',\n progress: clamp(0, action.minimum, 1),\n }\n\n case 'trickle':\n // Clamped here rather than trusted from the increment function, so a\n // custom one cannot take the bar outside the documented range. Stopping\n // short of 1 is that function's own business: 1 is what completion\n // means.\n return {\n ...state,\n progress: clamp(action.increment(state.progress), action.minimum, 1),\n }\n }\n}\n\nexport const useNProgress = ({\n animationDuration = 200,\n increment = defaultIncrement,\n incrementDuration = 200,\n isAnimating = false,\n minimum = 0.08,\n}: NProgressOptions = {}): NProgressState => {\n const [{ phase, progress }, dispatch] = useReducer(reducer, initialState)\n\n // Held in a ref so the trickle timer can read the latest increment function\n // without listing it as a dependency. Consumers commonly pass an inline\n // function, whose identity changes every render; depending on it directly\n // would cancel and recreate the timer each time, and a render loop faster\n // than `incrementDuration` would stop the bar advancing altogether.\n const incrementRef = useRef(increment)\n useEffect(() => {\n incrementRef.current = increment\n })\n\n useEffect(() => {\n dispatch(isAnimating ? { minimum, type: 'start' } : { type: 'complete' })\n }, [isAnimating, minimum])\n\n // A timer per running phase, rather than one effect branching over both.\n // Each then depends only on the options its own phase reads, so changing an\n // option the running phase ignores cannot cancel its timer. Both are keyed\n // on the phase rather than on progress, so trickling does not tear down and\n // recreate the timer mid-animation.\n useEffect(() => {\n if (phase !== 'animating') {\n return\n }\n\n const timeout = createTimeout()\n\n const trickle = () => {\n dispatch({ increment: incrementRef.current, minimum, type: 'trickle' })\n timeout.schedule(trickle, incrementDuration)\n }\n timeout.schedule(trickle, incrementDuration)\n\n return () => timeout.cancel()\n }, [incrementDuration, minimum, phase])\n\n useEffect(() => {\n if (phase !== 'completing') {\n return\n }\n\n const timeout = createTimeout()\n timeout.schedule(() => dispatch({ type: 'finish' }), animationDuration)\n\n return () => timeout.cancel()\n }, [animationDuration, phase])\n\n return {\n animationDuration,\n isFinished: phase === 'finished' || phase === 'idle',\n progress,\n }\n}\n","import type { FC, ReactElement } from 'react'\n\nimport type { NProgressOptions, NProgressState } from './types'\nimport { useNProgress } from './useNProgress'\n\ntype Props = NProgressOptions & {\n children: (renderProps: NProgressState) => ReactElement\n}\n\nexport const NProgress: FC<Props> = ({ children, ...restProps }: Props) => {\n const renderProps = useNProgress(restProps)\n return children(renderProps)\n}\n"],"mappings":";;;AAAA,MAAa,SAAS,KAAa,OAAe,UAA0B;CAC1E,MAAM,OAAO,QAAQ,MAAM;CAC3B,MAAM,OAAO,QAAQ,MAAM;CAC3B,OAAO;AACT;;;ACDA,MAAa,sBAAsB;CACjC,IAAI;CAEJ,MAAM,eAAqB;EACzB,IAAI,WAAW,KAAA,GACb,OAAO,qBAAqB,MAAM;CAEtC;CAEA,MAAM,YAAY,UAAsB,UAAwB;EAC9D,OAAO;EAEP,IAAI;EACJ,IAAI;EACJ,MAAM,SAA+B,SAAS;GAC5C,QAAQ,SAAS;GACjB,YAAY,OAAO;GACnB,IAAI,YAAY,OAAO;IACrB,SAAS;IACT;GACF;GACA,SAAS,OAAO,sBAAsB,KAAK;EAC7C;EAEA,SAAS,OAAO,sBAAsB,KAAK;CAC7C;CAEA,OAAO;EACL;EACA;CACF;AACF;;;AChCA,MAAa,aAAa,aAA6B;CACrD,IAAI,SAAS;CAEb,IAAI,YAAY,KAAK,WAAW,IAC9B,SAAS;MACJ,IAAI,YAAY,MAAO,WAAW,IACvC,SAAS;MACJ,IAAI,YAAY,MAAO,WAAW,IACvC,SAAS;MACJ,IAAI,YAAY,MAAO,WAAW,KACvC,SAAS;CAGX,OAAO,MAAM,WAAW,QAAQ,GAAG,IAAK;AAC1C;;;ACWA,MAAM,eAAsB;CAC1B,OAAO;CACP,UAAU;AACZ;AAEA,MAAM,WAAW,OAAc,WAA0B;CACvD,QAAQ,OAAO,MAAf;EACE,KAAK,YAQH,OAAO,MAAM,UAAU,cACnB;GAAE,OAAO;GAAc,UAAU;EAAE,IACnC;EAEN,KAAK,UACH,OAAO;GAAE,OAAO;GAAY,UAAU;EAAE;EAE1C,KAAK,SAQH,OAAO,MAAM,UAAU,cACnB,QACA;GACE,OAAO;GACP,UAAU,MAAM,GAAG,OAAO,SAAS,CAAC;EACtC;EAEN,KAAK,WAKH,OAAO;GACL,GAAG;GACH,UAAU,MAAM,OAAO,UAAU,MAAM,QAAQ,GAAG,OAAO,SAAS,CAAC;EACrE;CACJ;AACF;AAEA,MAAa,gBAAgB,EAC3B,oBAAoB,KACpB,WAAA,cAAYA,WACZ,oBAAoB,KACpB,cAAc,OACd,UAAU,QACU,CAAC,MAAsB;CAC3C,MAAM,CAAC,EAAE,OAAO,YAAY,YAAY,WAAW,SAAS,YAAY;CAOxE,MAAM,eAAe,OAAOC,WAAS;CACrC,gBAAgB;EACd,aAAa,UAAUA;CACzB,CAAC;CAED,gBAAgB;EACd,SAAS,cAAc;GAAE;GAAS,MAAM;EAAQ,IAAI,EAAE,MAAM,WAAW,CAAC;CAC1E,GAAG,CAAC,aAAa,OAAO,CAAC;CAOzB,gBAAgB;EACd,IAAI,UAAU,aACZ;EAGF,MAAM,UAAU,cAAc;EAE9B,MAAM,gBAAgB;GACpB,SAAS;IAAE,WAAW,aAAa;IAAS;IAAS,MAAM;GAAU,CAAC;GACtE,QAAQ,SAAS,SAAS,iBAAiB;EAC7C;EACA,QAAQ,SAAS,SAAS,iBAAiB;EAE3C,aAAa,QAAQ,OAAO;CAC9B,GAAG;EAAC;EAAmB;EAAS;CAAK,CAAC;CAEtC,gBAAgB;EACd,IAAI,UAAU,cACZ;EAGF,MAAM,UAAU,cAAc;EAC9B,QAAQ,eAAe,SAAS,EAAE,MAAM,SAAS,CAAC,GAAG,iBAAiB;EAEtE,aAAa,QAAQ,OAAO;CAC9B,GAAG,CAAC,mBAAmB,KAAK,CAAC;CAE7B,OAAO;EACL;EACA,YAAY,UAAU,cAAc,UAAU;EAC9C;CACF;AACF;;;AC/HA,MAAa,aAAwB,EAAE,UAAU,GAAG,gBAAuB;CAEzE,OAAO,SADa,aAAa,SACP,CAAC;AAC7B"}
|
package/package.json
CHANGED
package/src/types.ts
CHANGED
package/src/useNProgress.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { useEffect, useReducer } from 'react'
|
|
1
|
+
import { useEffect, useReducer, useRef } from 'react'
|
|
2
2
|
|
|
3
3
|
import { clamp } from './clamp'
|
|
4
4
|
import { createTimeout } from './createTimeout'
|
|
5
|
-
import { increment } from './increment'
|
|
5
|
+
import { increment as defaultIncrement } from './increment'
|
|
6
6
|
import type { NProgressOptions, NProgressState } from './types'
|
|
7
7
|
|
|
8
8
|
// A four-phase state machine. `idle` and `finished` both report
|
|
@@ -16,8 +16,12 @@ interface State {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
type Action =
|
|
19
|
+
| {
|
|
20
|
+
increment: (progress: number) => number
|
|
21
|
+
minimum: number
|
|
22
|
+
type: 'trickle'
|
|
23
|
+
}
|
|
19
24
|
| { minimum: number; type: 'start' }
|
|
20
|
-
| { minimum: number; type: 'trickle' }
|
|
21
25
|
| { type: 'complete' }
|
|
22
26
|
| { type: 'finish' }
|
|
23
27
|
|
|
@@ -29,10 +33,10 @@ const initialState: State = {
|
|
|
29
33
|
const reducer = (state: State, action: Action): State => {
|
|
30
34
|
switch (action.type) {
|
|
31
35
|
case 'complete':
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
+
// The original nprogress `done()` computes a random progress jump before
|
|
37
|
+
// animating to 1, but its queue runs both steps in the same tick, so the
|
|
38
|
+
// jump's CSS is overwritten before paint and never renders. Omitting the
|
|
39
|
+
// jump changes nothing visually.
|
|
36
40
|
//
|
|
37
41
|
// Ignored unless an animation is actually running, which is what makes a
|
|
38
42
|
// StrictMode double-mount a no-op rather than a spurious completion.
|
|
@@ -44,11 +48,8 @@ const reducer = (state: State, action: Action): State => {
|
|
|
44
48
|
return { phase: 'finished', progress: 1 }
|
|
45
49
|
|
|
46
50
|
case 'start':
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
// increment(0) = 0.1, so the bar appears at max(0.1, minimum) rather
|
|
50
|
-
// than exactly `minimum`. The difference is negligible at the default
|
|
51
|
-
// minimum of 0.08.
|
|
51
|
+
// Matches the original nprogress `start()`, which calls set(0) and so
|
|
52
|
+
// paints first at `minimum` before any trickle runs.
|
|
52
53
|
//
|
|
53
54
|
// Guarded the same way as `complete`, and for the same reason: a repeat
|
|
54
55
|
// dispatch against a running animation must not rewind the bar. That
|
|
@@ -58,25 +59,40 @@ const reducer = (state: State, action: Action): State => {
|
|
|
58
59
|
? state
|
|
59
60
|
: {
|
|
60
61
|
phase: 'animating',
|
|
61
|
-
progress: clamp(
|
|
62
|
+
progress: clamp(0, action.minimum, 1),
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
case 'trickle':
|
|
66
|
+
// Clamped here rather than trusted from the increment function, so a
|
|
67
|
+
// custom one cannot take the bar outside the documented range. Stopping
|
|
68
|
+
// short of 1 is that function's own business: 1 is what completion
|
|
69
|
+
// means.
|
|
65
70
|
return {
|
|
66
71
|
...state,
|
|
67
|
-
progress: clamp(increment(state.progress), action.minimum, 1),
|
|
72
|
+
progress: clamp(action.increment(state.progress), action.minimum, 1),
|
|
68
73
|
}
|
|
69
74
|
}
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
export const useNProgress = ({
|
|
73
78
|
animationDuration = 200,
|
|
79
|
+
increment = defaultIncrement,
|
|
74
80
|
incrementDuration = 200,
|
|
75
81
|
isAnimating = false,
|
|
76
82
|
minimum = 0.08,
|
|
77
83
|
}: NProgressOptions = {}): NProgressState => {
|
|
78
84
|
const [{ phase, progress }, dispatch] = useReducer(reducer, initialState)
|
|
79
85
|
|
|
86
|
+
// Held in a ref so the trickle timer can read the latest increment function
|
|
87
|
+
// without listing it as a dependency. Consumers commonly pass an inline
|
|
88
|
+
// function, whose identity changes every render; depending on it directly
|
|
89
|
+
// would cancel and recreate the timer each time, and a render loop faster
|
|
90
|
+
// than `incrementDuration` would stop the bar advancing altogether.
|
|
91
|
+
const incrementRef = useRef(increment)
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
incrementRef.current = increment
|
|
94
|
+
})
|
|
95
|
+
|
|
80
96
|
useEffect(() => {
|
|
81
97
|
dispatch(isAnimating ? { minimum, type: 'start' } : { type: 'complete' })
|
|
82
98
|
}, [isAnimating, minimum])
|
|
@@ -94,7 +110,7 @@ export const useNProgress = ({
|
|
|
94
110
|
const timeout = createTimeout()
|
|
95
111
|
|
|
96
112
|
const trickle = () => {
|
|
97
|
-
dispatch({ minimum, type: 'trickle' })
|
|
113
|
+
dispatch({ increment: incrementRef.current, minimum, type: 'trickle' })
|
|
98
114
|
timeout.schedule(trickle, incrementDuration)
|
|
99
115
|
}
|
|
100
116
|
timeout.schedule(trickle, incrementDuration)
|