@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 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 four options are optional. The type is `NProgressOptions`.
135
+ All five options are optional. The type is `NProgressOptions`.
114
136
 
115
- | Option | Type | Default |
116
- | ----------------------------------------- | --------- | ------- |
117
- | [`animationDuration`](#animationduration) | `number` | `200` |
118
- | [`incrementDuration`](#incrementduration) | `number` | `200` |
119
- | [`isAnimating`](#isanimating) | `boolean` | `false` |
120
- | [`minimum`](#minimum) | `number` | `0.08` |
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: the size of each increment is not configurable, and shrinks as `progress` grows.
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 increment starts from `0.1` rather than from `0`, so the bar appears at `max(0.1, minimum)` and the option only shows through when it is set above `0.1`. 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.
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` and trickles up in shrinking steps to a ceiling of `0.994`, then goes to `1` on completion. |
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) |
@@ -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(increment(0), action.minimum, 1)
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; type: 'start' }\n | { minimum: number; type: 'trickle' }\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 // Unlike the original nprogress `done()`, completion does not include a\n // random progress jump before animating to 1. This keeps the primitive\n // predictable; consumers can set a higher progress value before stopping\n // the animation if they want that effect.\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 // The original nprogress calls set(0) - which clamps to `minimum` -\n // before the first trickle. Here, the first trickle starts from\n // increment(0) = 0.1, so the bar appears at max(0.1, minimum) rather\n // than exactly `minimum`. The difference is negligible at the default\n // minimum of 0.08.\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(increment(0), action.minimum, 1),\n }\n\n case 'trickle':\n return {\n ...state,\n progress: clamp(increment(state.progress), action.minimum, 1),\n }\n }\n}\n\nexport const useNProgress = ({\n animationDuration = 200,\n incrementDuration = 200,\n isAnimating = false,\n minimum = 0.08,\n}: NProgressOptions = {}): NProgressState => {\n const [{ phase, progress }, dispatch] = useReducer(reducer, initialState)\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({ 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;;;ACOA,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,SAWH,OAAO,MAAM,UAAU,cACnB,QACA;GACE,OAAO;GACP,UAAU,MAAM,UAAU,CAAC,GAAG,OAAO,SAAS,CAAC;EACjD;EAEN,KAAK,WACH,OAAO;GACL,GAAG;GACH,UAAU,MAAM,UAAU,MAAM,QAAQ,GAAG,OAAO,SAAS,CAAC;EAC9D;CACJ;AACF;AAEA,MAAa,gBAAgB,EAC3B,oBAAoB,KACpB,oBAAoB,KACpB,cAAc,OACd,UAAU,QACU,CAAC,MAAsB;CAC3C,MAAM,CAAC,EAAE,OAAO,YAAY,aAAA,GAAYA,MAAAA,WAAAA,CAAW,SAAS,YAAY;CAExE,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,SAAS,cAAc;GAAE;GAAS,MAAM;EAAQ,IAAI,EAAE,MAAM,WAAW,CAAC;CAC1E,GAAG,CAAC,aAAa,OAAO,CAAC;CAOzB,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,IAAI,UAAU,aACZ;EAGF,MAAM,UAAU,cAAc;EAE9B,MAAM,gBAAgB;GACpB,SAAS;IAAE;IAAS,MAAM;GAAU,CAAC;GACrC,QAAQ,SAAS,SAAS,iBAAiB;EAC7C;EACA,QAAQ,SAAS,SAAS,iBAAiB;EAE3C,aAAa,QAAQ,OAAO;CAC9B,GAAG;EAAC;EAAmB;EAAS;CAAK,CAAC;CAEtC,CAAA,GAAA,MAAA,UAAA,OAAgB;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/GA,MAAa,aAAwB,EAAE,UAAU,GAAG,gBAAuB;CAEzE,OAAO,SADa,aAAa,SACP,CAAC;AAC7B"}
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":";;;;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,aAAA,GAAYC,MAAAA,WAAAA,CAAW,SAAS,YAAY;CAOxE,MAAM,gBAAA,GAAeC,MAAAA,OAAAA,CAAOC,WAAS;CACrC,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,aAAa,UAAUA;CACzB,CAAC;CAED,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,SAAS,cAAc;GAAE;GAAS,MAAM;EAAQ,IAAI,EAAE,MAAM,WAAW,CAAC;CAC1E,GAAG,CAAC,aAAa,OAAO,CAAC;CAOzB,CAAA,GAAA,MAAA,UAAA,OAAgB;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,CAAA,GAAA,MAAA,UAAA,OAAgB;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"}
@@ -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;;;;KCLG,QAAQ;EACX,WAAW,aAAa,mBAAmB;;cAGhC,WAAW,GAAG;;;cC8Dd,iBAAgB,mBAAA,mBAAA,aAAA,YAK1B,qBAAwB"}
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;;;;KCLG,QAAQ;EACX,WAAW,aAAa,mBAAmB;;cAGhC,WAAW,GAAG;;;cC8Dd,iBAAgB,mBAAA,mBAAA,aAAA,YAK1B,qBAAwB"}
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"}
@@ -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(increment(0), action.minimum, 1)
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; type: 'start' }\n | { minimum: number; type: 'trickle' }\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 // Unlike the original nprogress `done()`, completion does not include a\n // random progress jump before animating to 1. This keeps the primitive\n // predictable; consumers can set a higher progress value before stopping\n // the animation if they want that effect.\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 // The original nprogress calls set(0) - which clamps to `minimum` -\n // before the first trickle. Here, the first trickle starts from\n // increment(0) = 0.1, so the bar appears at max(0.1, minimum) rather\n // than exactly `minimum`. The difference is negligible at the default\n // minimum of 0.08.\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(increment(0), action.minimum, 1),\n }\n\n case 'trickle':\n return {\n ...state,\n progress: clamp(increment(state.progress), action.minimum, 1),\n }\n }\n}\n\nexport const useNProgress = ({\n animationDuration = 200,\n incrementDuration = 200,\n isAnimating = false,\n minimum = 0.08,\n}: NProgressOptions = {}): NProgressState => {\n const [{ phase, progress }, dispatch] = useReducer(reducer, initialState)\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({ 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;;;ACOA,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,SAWH,OAAO,MAAM,UAAU,cACnB,QACA;GACE,OAAO;GACP,UAAU,MAAM,UAAU,CAAC,GAAG,OAAO,SAAS,CAAC;EACjD;EAEN,KAAK,WACH,OAAO;GACL,GAAG;GACH,UAAU,MAAM,UAAU,MAAM,QAAQ,GAAG,OAAO,SAAS,CAAC;EAC9D;CACJ;AACF;AAEA,MAAa,gBAAgB,EAC3B,oBAAoB,KACpB,oBAAoB,KACpB,cAAc,OACd,UAAU,QACU,CAAC,MAAsB;CAC3C,MAAM,CAAC,EAAE,OAAO,YAAY,YAAY,WAAW,SAAS,YAAY;CAExE,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;IAAS,MAAM;GAAU,CAAC;GACrC,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/GA,MAAa,aAAwB,EAAE,UAAU,GAAG,gBAAuB;CAEzE,OAAO,SADa,aAAa,SACP,CAAC;AAC7B"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanem/react-nprogress",
3
- "version": "7.0.0",
3
+ "version": "7.1.0",
4
4
  "description": "A React primitive for building slim progress bars.",
5
5
  "type": "commonjs",
6
6
  "exports": {
package/src/types.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export interface NProgressOptions {
2
2
  animationDuration?: number
3
+ increment?: (progress: number) => number
3
4
  incrementDuration?: number
4
5
  isAnimating?: boolean
5
6
  minimum?: number
@@ -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
- // Unlike the original nprogress `done()`, completion does not include a
33
- // random progress jump before animating to 1. This keeps the primitive
34
- // predictable; consumers can set a higher progress value before stopping
35
- // the animation if they want that effect.
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
- // The original nprogress calls set(0) - which clamps to `minimum` -
48
- // before the first trickle. Here, the first trickle starts from
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(increment(0), action.minimum, 1),
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)