@vctqs1/nav-progress-bar 0.0.2-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 +278 -0
- package/dist/index.cjs +23 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +111 -0
- package/dist/lib/nav-progress-bar.d.ts +115 -0
- package/dist/lib/nav-progress-bar.d.ts.map +1 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# @vctqs1/nav-progress-bar
|
|
2
|
+
|
|
3
|
+
A zero-dependency, CSP-safe top-of-page progress bar built as a native Web Component. Works in any framework — or no framework at all.
|
|
4
|
+
|
|
5
|
+
> Originally built to solve the [Next.js App Router `loading.js` dead gap](https://github.com/vercel/next.js/issues/43548), but the underlying mechanism (the browser [Navigation API](https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API)) works anywhere.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Zero dependencies** — pure Web Component, no React, no Vue, nothing
|
|
10
|
+
- **CSP-safe** — all styling via `adoptedStyleSheets`, no inline `style=` or `<style>` tags
|
|
11
|
+
- **SSR-ready** — supports Declarative Shadow DOM, no flash on hydration
|
|
12
|
+
- **Framework-agnostic** — Next.js, Nuxt, SvelteKit, Astro, Remix, vanilla HTML
|
|
13
|
+
- **Auto start/finish** — listens to browser Navigation API events automatically
|
|
14
|
+
- **Customisable color** — raw hex or CSS custom property
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @vctqs1/nav-progress-bar
|
|
20
|
+
# or
|
|
21
|
+
pnpm add @vctqs1/nav-progress-bar
|
|
22
|
+
# or
|
|
23
|
+
yarn add @vctqs1/nav-progress-bar
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### Vanilla HTML
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<script type="module">
|
|
32
|
+
import { registerNavProgressBar } from '@vctqs1/nav-progress-bar';
|
|
33
|
+
registerNavProgressBar();
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<vctqs1-nav-progress-bar></vctqs1-nav-progress-bar>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### ES Module
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { registerNavProgressBar, getNavProgressBar } from '@vctqs1/nav-progress-bar';
|
|
43
|
+
|
|
44
|
+
registerNavProgressBar();
|
|
45
|
+
|
|
46
|
+
// The bar auto-starts on navigate and auto-finishes on navigatesuccess.
|
|
47
|
+
// You can also control it manually:
|
|
48
|
+
getNavProgressBar()?.start();
|
|
49
|
+
getNavProgressBar()?.finish();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Framework Guides
|
|
53
|
+
|
|
54
|
+
### Next.js App Router
|
|
55
|
+
|
|
56
|
+
The bar solves the `loading.js` dead gap — the frozen period between a user clicking a link and any loading feedback appearing. See [Next.js issue #43548](https://github.com/vercel/next.js/issues/43548).
|
|
57
|
+
|
|
58
|
+
**1. Add the React wrapper** (handles SSR rendering):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm install @vctqs1/nav-progress-bar-react
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**2. Add to your root layout** (`app/layout.tsx`):
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import NavProgressBar from '@vctqs1/nav-progress-bar-react';
|
|
68
|
+
|
|
69
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
70
|
+
return (
|
|
71
|
+
<html>
|
|
72
|
+
<body>
|
|
73
|
+
<NavProgressBar />
|
|
74
|
+
{children}
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**3. Create `instrumentation-client.ts`** at the project root:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { registerNavProgressBar, getNavProgressBar } from '@vctqs1/nav-progress-bar';
|
|
85
|
+
|
|
86
|
+
registerNavProgressBar();
|
|
87
|
+
|
|
88
|
+
export function onRouterTransitionStart(
|
|
89
|
+
url: string,
|
|
90
|
+
navigationType: 'push' | 'replace' | 'traverse',
|
|
91
|
+
) {
|
|
92
|
+
getNavProgressBar()?.start();
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**4. Enable instrumentation** in `next.config.ts`:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const nextConfig = {
|
|
100
|
+
experimental: {
|
|
101
|
+
instrumentationHook: true,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export default nextConfig;
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
How the bar completes without a "done" callback — Next.js calls `history.pushState()` to update the URL, which fires the browser `navigatesuccess` event as a side effect. The web component listens for this internally and calls `finish()` automatically.
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
User clicks <Link>
|
|
112
|
+
→ "navigate" fires instantly → bar starts (0ms)
|
|
113
|
+
→ Next.js fetches RSC payload
|
|
114
|
+
→ React patches the tree
|
|
115
|
+
→ history.pushState() finalises
|
|
116
|
+
→ "navigatesuccess" fires → bar finishes
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### Nuxt
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// plugins/nav-progress-bar.client.ts
|
|
125
|
+
import { registerNavProgressBar, getNavProgressBar } from '@vctqs1/nav-progress-bar';
|
|
126
|
+
|
|
127
|
+
export default defineNuxtPlugin(() => {
|
|
128
|
+
registerNavProgressBar();
|
|
129
|
+
|
|
130
|
+
const router = useRouter();
|
|
131
|
+
router.beforeEach(() => getNavProgressBar()?.start());
|
|
132
|
+
// finish() is handled automatically by navigatesuccess
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```html
|
|
137
|
+
<!-- app.vue -->
|
|
138
|
+
<template>
|
|
139
|
+
<div>
|
|
140
|
+
<vctqs1-nav-progress-bar></vctqs1-nav-progress-bar>
|
|
141
|
+
<NuxtPage />
|
|
142
|
+
</div>
|
|
143
|
+
</template>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
### SvelteKit
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
// src/hooks.client.ts
|
|
152
|
+
import { registerNavProgressBar } from '@vctqs1/nav-progress-bar';
|
|
153
|
+
registerNavProgressBar();
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```svelte
|
|
157
|
+
<!-- src/routes/+layout.svelte -->
|
|
158
|
+
<vctqs1-nav-progress-bar></vctqs1-nav-progress-bar>
|
|
159
|
+
<slot />
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### Astro
|
|
165
|
+
|
|
166
|
+
```astro
|
|
167
|
+
---
|
|
168
|
+
// src/layouts/BaseLayout.astro
|
|
169
|
+
---
|
|
170
|
+
<html>
|
|
171
|
+
<body>
|
|
172
|
+
<vctqs1-nav-progress-bar></vctqs1-nav-progress-bar>
|
|
173
|
+
<slot />
|
|
174
|
+
<script>
|
|
175
|
+
import { registerNavProgressBar } from '@vctqs1/nav-progress-bar';
|
|
176
|
+
registerNavProgressBar();
|
|
177
|
+
</script>
|
|
178
|
+
</body>
|
|
179
|
+
</html>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### Vanilla HTML
|
|
185
|
+
|
|
186
|
+
```html
|
|
187
|
+
<!DOCTYPE html>
|
|
188
|
+
<html>
|
|
189
|
+
<body>
|
|
190
|
+
<vctqs1-nav-progress-bar></vctqs1-nav-progress-bar>
|
|
191
|
+
|
|
192
|
+
<script type="module">
|
|
193
|
+
import { registerNavProgressBar } from '@vctqs1/nav-progress-bar';
|
|
194
|
+
registerNavProgressBar();
|
|
195
|
+
</script>
|
|
196
|
+
</body>
|
|
197
|
+
</html>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Color Configuration
|
|
201
|
+
|
|
202
|
+
Two formats are supported for the `primary` attribute:
|
|
203
|
+
|
|
204
|
+
```html
|
|
205
|
+
<!-- Raw hex or any CSS color -->
|
|
206
|
+
<vctqs1-nav-progress-bar primary="#006bde"></vctqs1-nav-progress-bar>
|
|
207
|
+
|
|
208
|
+
<!-- CSS custom property (resolved against document styles) -->
|
|
209
|
+
<vctqs1-nav-progress-bar primary="--your-brand-color"></vctqs1-nav-progress-bar>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
When a CSS variable name is passed, the component wraps it in `var(--token, fallback)` inside its Shadow DOM — it inherits custom properties from the document since Shadow DOM does not isolate inherited CSS variables.
|
|
213
|
+
|
|
214
|
+
### Priority Order
|
|
215
|
+
|
|
216
|
+
1. HTML attribute `primary="..."` — runtime, per-element (highest)
|
|
217
|
+
2. `registerNavProgressBar({ primary: '...' })` — build-time default
|
|
218
|
+
3. Hardcoded fallback `#006bde` (lowest)
|
|
219
|
+
|
|
220
|
+
## API
|
|
221
|
+
|
|
222
|
+
### `registerNavProgressBar(options?)`
|
|
223
|
+
|
|
224
|
+
Registers the `<vctqs1-nav-progress-bar>` custom element. Safe to call multiple times — subsequent calls are no-ops. Skips registration entirely if the browser does not support the Navigation API.
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
registerNavProgressBar();
|
|
228
|
+
registerNavProgressBar({ primary: '#fff' });
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### `getNavProgressBar()`
|
|
232
|
+
|
|
233
|
+
Returns the first `<vctqs1-nav-progress-bar>` element in the document, or `null`.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
getNavProgressBar()?.start();
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Instance Methods
|
|
240
|
+
|
|
241
|
+
| Method | Description |
|
|
242
|
+
|--------|-------------|
|
|
243
|
+
| `start()` | Begins indeterminate progress animation, returns `this` |
|
|
244
|
+
| `finish()` | Completes bar to 100% then fades out, returns `this` |
|
|
245
|
+
|
|
246
|
+
### HTML Attributes
|
|
247
|
+
|
|
248
|
+
| Attribute | Type | Default | Description |
|
|
249
|
+
|-----------|------|---------|-------------|
|
|
250
|
+
| `primary` | color or `--css-var` | `#006bde` | Bar fill color |
|
|
251
|
+
|
|
252
|
+
## How It Works
|
|
253
|
+
|
|
254
|
+
The component uses an asymptotic easing approach: every 200ms it moves 8% of the remaining distance toward 85%, never quite reaching it. When `finish()` is called, width jumps to 100% then fades out over 700ms. This gives the appearance of continuous progress without knowing actual load time.
|
|
255
|
+
|
|
256
|
+
All styling uses two `CSSStyleSheet` objects via `adoptedStyleSheets`:
|
|
257
|
+
|
|
258
|
+
- **Color sheet** — static rules for color, layout, and transitions. Rebuilt on attribute change.
|
|
259
|
+
- **Progress sheet** — single `.bar { width: N%; }` rule, rewritten via `replaceSync` on every tick. No rule accumulation, always authoritative.
|
|
260
|
+
|
|
261
|
+
## Browser Support
|
|
262
|
+
|
|
263
|
+
Requires the [Navigation API](https://caniuse.com/mdn-api_navigation) — Chrome 102+, Edge 102+. Firefox and Safari do not support it yet; registration is skipped gracefully in unsupported browsers (no errors, no stuck bar).
|
|
264
|
+
|
|
265
|
+
## Debugging
|
|
266
|
+
|
|
267
|
+
Test manually in the browser console:
|
|
268
|
+
|
|
269
|
+
```js
|
|
270
|
+
document.querySelector('vctqs1-nav-progress-bar').start();
|
|
271
|
+
document.querySelector('vctqs1-nav-progress-bar').finish();
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Inspect the Shadow DOM in DevTools — Chrome and Firefox both expose Shadow DOM roots in the Elements panel under the `<vctqs1-nav-progress-bar>` element.
|
|
275
|
+
|
|
276
|
+
## License
|
|
277
|
+
|
|
278
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(){let e=globalThis.navigation;if(!(!e||typeof e!=`object`)&&!(!(`addEventListener`in e)||!(`removeEventListener`in e)))return e}var t=class extends HTMLElement{_layoutSheet=void 0;_sheet=void 0;_progressSheet=void 0;_autoTimer=void 0;_startTime=void 0;_options;static get observedAttributes(){return[`primary`]}constructor(e={}){super(),this._options=e}_onNavigateSuccess=()=>this.finish();_onNavigate=()=>this.start();connectedCallback(){this._render();let t=e();t&&(t.addEventListener(`navigate`,this._onNavigate),t.addEventListener(`navigatesuccess`,this._onNavigateSuccess))}disconnectedCallback(){let t=e();t&&(t.removeEventListener(`navigate`,this._onNavigate),t.removeEventListener(`navigatesuccess`,this._onNavigateSuccess)),this._clearTimer()}attributeChangedCallback(){this._sheet&&this._applyColorSheet()}_resolveColor(e,t,n){let r=e??t;return r?r.startsWith(`--`)?`var(${r}, ${n})`:r:n}_buildColorSheet(){let e=this._resolveColor(this.getAttribute(`primary`),this._options.primary,`#006bde`),t=new CSSStyleSheet;return t.insertRule(`
|
|
2
|
+
.bar {
|
|
3
|
+
height: 3px;
|
|
4
|
+
background: ${e};
|
|
5
|
+
opacity: 0;
|
|
6
|
+
transition: width 0.3s ease, opacity 0.4s ease;
|
|
7
|
+
box-shadow: 0 0 8px color-mix(in srgb, ${e} 53%, transparent);
|
|
8
|
+
}
|
|
9
|
+
`),t.insertRule(`.bar.active { opacity: 1; }`),t.insertRule(`
|
|
10
|
+
.bar.complete {
|
|
11
|
+
opacity: 0;
|
|
12
|
+
transition: width 0.2s ease, opacity 0.5s ease 0.2s;
|
|
13
|
+
}
|
|
14
|
+
`),t}_applyColorSheet(){!this.shadowRoot||!this._layoutSheet||!this._progressSheet||(this._sheet=this._buildColorSheet(),this.shadowRoot.adoptedStyleSheets=[this._layoutSheet,this._sheet,this._progressSheet])}_render(){let e=this.shadowRoot??this.attachShadow({mode:`open`}),t=new CSSStyleSheet;t.insertRule(`
|
|
15
|
+
:host {
|
|
16
|
+
position: fixed;
|
|
17
|
+
top: 0;
|
|
18
|
+
left: 0;
|
|
19
|
+
width: 100%;
|
|
20
|
+
z-index: 9999;
|
|
21
|
+
pointer-events: none;
|
|
22
|
+
}
|
|
23
|
+
`),this._layoutSheet=t;let n=new CSSStyleSheet;if(n.insertRule(`.bar { width: 0%; }`),this._progressSheet=n,this._sheet=this._buildColorSheet(),e.adoptedStyleSheets=[this._layoutSheet,this._sheet,this._progressSheet],!e.querySelector(`.bar`)){let t=document.createElement(`div`);t.className=`bar`,e.append(t)}}_setWidth(e){this._progressSheet&&this._progressSheet.replaceSync(`.bar { width: ${e}%; }`)}start(){this._clearTimer(),this._setWidth(0),this._applyActive(!0);let e=0;return this._autoTimer=setInterval(()=>{e+=(85-e)*.08,this._setWidth(Math.min(e,85))},200),this}finish(){this._clearTimer();let e=this.shadowRoot?.querySelector(`.bar`);return e?(e.classList.remove(`active`),e.classList.add(`complete`),this._setWidth(100),setTimeout(()=>{e.classList.remove(`complete`),this._setWidth(0),this._applyActive(!1)},700),this):this}_applyActive(e){let t=this.shadowRoot?.querySelector(`.bar`);if(t&&(t.classList.toggle(`active`,e),e&&this._startTime===void 0&&(this._startTime=performance.now()),!e&&this._startTime!==void 0)){let e=performance.now()-this._startTime;console.log(`[top-loading-bar] navigation took ${e.toFixed(0)}ms`),this._startTime=void 0}}_clearTimer(){this._autoTimer!==void 0&&(clearInterval(this._autoTimer),this._autoTimer=void 0)}},n=`vctqs1-nav-progress-bar`;function r(r){if(!(typeof customElements>`u`)&&e()&&!customElements.get(`vctqs1-nav-progress-bar`)){class e extends t{constructor(){super(r)}}customElements.define(n,e)}}function i(){return document.querySelector(n)}exports.NavProgressBar=t,exports.TAG_NAME=n,exports.getNavProgressBar=i,exports.registerNavProgressBar=r;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
//#region src/lib/nav-progress-bar.ts
|
|
2
|
+
function e() {
|
|
3
|
+
let e = globalThis.navigation;
|
|
4
|
+
if (!(!e || typeof e != "object") && !(!("addEventListener" in e) || !("removeEventListener" in e))) return e;
|
|
5
|
+
}
|
|
6
|
+
var t = class extends HTMLElement {
|
|
7
|
+
_layoutSheet = void 0;
|
|
8
|
+
_sheet = void 0;
|
|
9
|
+
_progressSheet = void 0;
|
|
10
|
+
_autoTimer = void 0;
|
|
11
|
+
_startTime = void 0;
|
|
12
|
+
_options;
|
|
13
|
+
static get observedAttributes() {
|
|
14
|
+
return ["primary"];
|
|
15
|
+
}
|
|
16
|
+
constructor(e = {}) {
|
|
17
|
+
super(), this._options = e;
|
|
18
|
+
}
|
|
19
|
+
_onNavigateSuccess = () => this.finish();
|
|
20
|
+
_onNavigate = () => this.start();
|
|
21
|
+
connectedCallback() {
|
|
22
|
+
this._render();
|
|
23
|
+
let t = e();
|
|
24
|
+
t && (t.addEventListener("navigate", this._onNavigate), t.addEventListener("navigatesuccess", this._onNavigateSuccess));
|
|
25
|
+
}
|
|
26
|
+
disconnectedCallback() {
|
|
27
|
+
let t = e();
|
|
28
|
+
t && (t.removeEventListener("navigate", this._onNavigate), t.removeEventListener("navigatesuccess", this._onNavigateSuccess)), this._clearTimer();
|
|
29
|
+
}
|
|
30
|
+
attributeChangedCallback() {
|
|
31
|
+
this._sheet && this._applyColorSheet();
|
|
32
|
+
}
|
|
33
|
+
_resolveColor(e, t, n) {
|
|
34
|
+
let r = e ?? t;
|
|
35
|
+
return r ? r.startsWith("--") ? `var(${r}, ${n})` : r : n;
|
|
36
|
+
}
|
|
37
|
+
_buildColorSheet() {
|
|
38
|
+
let e = this._resolveColor(this.getAttribute("primary"), this._options.primary, "#006bde"), t = new CSSStyleSheet();
|
|
39
|
+
return t.insertRule(`
|
|
40
|
+
.bar {
|
|
41
|
+
height: 3px;
|
|
42
|
+
background: ${e};
|
|
43
|
+
opacity: 0;
|
|
44
|
+
transition: width 0.3s ease, opacity 0.4s ease;
|
|
45
|
+
box-shadow: 0 0 8px color-mix(in srgb, ${e} 53%, transparent);
|
|
46
|
+
}
|
|
47
|
+
`), t.insertRule(".bar.active { opacity: 1; }"), t.insertRule("\n .bar.complete {\n opacity: 0;\n transition: width 0.2s ease, opacity 0.5s ease 0.2s;\n }\n "), t;
|
|
48
|
+
}
|
|
49
|
+
_applyColorSheet() {
|
|
50
|
+
!this.shadowRoot || !this._layoutSheet || !this._progressSheet || (this._sheet = this._buildColorSheet(), this.shadowRoot.adoptedStyleSheets = [
|
|
51
|
+
this._layoutSheet,
|
|
52
|
+
this._sheet,
|
|
53
|
+
this._progressSheet
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
_render() {
|
|
57
|
+
let e = this.shadowRoot ?? this.attachShadow({ mode: "open" }), t = new CSSStyleSheet();
|
|
58
|
+
t.insertRule("\n :host {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n z-index: 9999;\n pointer-events: none;\n }\n "), this._layoutSheet = t;
|
|
59
|
+
let n = new CSSStyleSheet();
|
|
60
|
+
if (n.insertRule(".bar { width: 0%; }"), this._progressSheet = n, this._sheet = this._buildColorSheet(), e.adoptedStyleSheets = [
|
|
61
|
+
this._layoutSheet,
|
|
62
|
+
this._sheet,
|
|
63
|
+
this._progressSheet
|
|
64
|
+
], !e.querySelector(".bar")) {
|
|
65
|
+
let t = document.createElement("div");
|
|
66
|
+
t.className = "bar", e.append(t);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
_setWidth(e) {
|
|
70
|
+
this._progressSheet && this._progressSheet.replaceSync(`.bar { width: ${e}%; }`);
|
|
71
|
+
}
|
|
72
|
+
start() {
|
|
73
|
+
this._clearTimer(), this._setWidth(0), this._applyActive(!0);
|
|
74
|
+
let e = 0;
|
|
75
|
+
return this._autoTimer = setInterval(() => {
|
|
76
|
+
e += (85 - e) * .08, this._setWidth(Math.min(e, 85));
|
|
77
|
+
}, 200), this;
|
|
78
|
+
}
|
|
79
|
+
finish() {
|
|
80
|
+
this._clearTimer();
|
|
81
|
+
let e = this.shadowRoot?.querySelector(".bar");
|
|
82
|
+
return e ? (e.classList.remove("active"), e.classList.add("complete"), this._setWidth(100), setTimeout(() => {
|
|
83
|
+
e.classList.remove("complete"), this._setWidth(0), this._applyActive(!1);
|
|
84
|
+
}, 700), this) : this;
|
|
85
|
+
}
|
|
86
|
+
_applyActive(e) {
|
|
87
|
+
let t = this.shadowRoot?.querySelector(".bar");
|
|
88
|
+
if (t && (t.classList.toggle("active", e), e && this._startTime === void 0 && (this._startTime = performance.now()), !e && this._startTime !== void 0)) {
|
|
89
|
+
let e = performance.now() - this._startTime;
|
|
90
|
+
console.log(`[top-loading-bar] navigation took ${e.toFixed(0)}ms`), this._startTime = void 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
_clearTimer() {
|
|
94
|
+
this._autoTimer !== void 0 && (clearInterval(this._autoTimer), this._autoTimer = void 0);
|
|
95
|
+
}
|
|
96
|
+
}, n = "vctqs1-nav-progress-bar";
|
|
97
|
+
function r(r) {
|
|
98
|
+
if (!(typeof customElements > "u") && e() && !customElements.get("vctqs1-nav-progress-bar")) {
|
|
99
|
+
class e extends t {
|
|
100
|
+
constructor() {
|
|
101
|
+
super(r);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
customElements.define(n, e);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function i() {
|
|
108
|
+
return document.querySelector(n);
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
export { t as NavProgressBar, n as TAG_NAME, i as getNavProgressBar, r as registerNavProgressBar };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for configuring the NavProgressBar component.
|
|
3
|
+
*/
|
|
4
|
+
export interface NavProgressBarOptions {
|
|
5
|
+
/** Primary color of the progress bar (hex, rgb, or CSS custom property) */
|
|
6
|
+
primary?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A web component that displays a progress bar during navigation.
|
|
10
|
+
* Integrates with the Navigation API to automatically start/finish the progress bar.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { registerNavProgressBar } from '@vctqs1/nav-progress-bar';
|
|
15
|
+
* registerNavProgressBar({ primary: '#006bde' });
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Then use in HTML:
|
|
19
|
+
* ```html
|
|
20
|
+
* <nav-progress-bar primary="#006bde"></nav-progress-bar>
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* A web component that displays a progress bar during navigation.
|
|
25
|
+
* Integrates with the Navigation API to automatically start/finish the progress bar.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { registerNavProgressBar } from '@vctqs1/nav-progress-bar';
|
|
30
|
+
* registerNavProgressBar({ primary: '#006bde' });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Then use in HTML:
|
|
34
|
+
* ```html
|
|
35
|
+
* <nav-progress-bar primary="#006bde"></nav-progress-bar>
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare class NavProgressBar extends HTMLElement {
|
|
39
|
+
private _layoutSheet;
|
|
40
|
+
private _sheet;
|
|
41
|
+
private _progressSheet;
|
|
42
|
+
private _autoTimer;
|
|
43
|
+
private _startTime;
|
|
44
|
+
private _options;
|
|
45
|
+
static get observedAttributes(): string[];
|
|
46
|
+
/**
|
|
47
|
+
* Creates a new NavProgressBar instance.
|
|
48
|
+
* @param options Configuration options for the progress bar
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* Creates a new NavProgressBar instance.
|
|
52
|
+
* @param options Configuration options for the progress bar
|
|
53
|
+
*/
|
|
54
|
+
constructor(options?: NavProgressBarOptions);
|
|
55
|
+
private _onNavigateSuccess;
|
|
56
|
+
private _onNavigate;
|
|
57
|
+
connectedCallback(): void;
|
|
58
|
+
disconnectedCallback(): void;
|
|
59
|
+
attributeChangedCallback(): void;
|
|
60
|
+
private _resolveColor;
|
|
61
|
+
private _buildColorSheet;
|
|
62
|
+
private _applyColorSheet;
|
|
63
|
+
private _render;
|
|
64
|
+
private _setWidth;
|
|
65
|
+
/**
|
|
66
|
+
* Starts the progress animation.
|
|
67
|
+
* If already running, restarts from 0.
|
|
68
|
+
* @returns The element instance for chaining
|
|
69
|
+
*/
|
|
70
|
+
/**
|
|
71
|
+
* Starts the progress animation.
|
|
72
|
+
* If already running, restarts from 0.
|
|
73
|
+
* @returns The element instance for chaining
|
|
74
|
+
*/
|
|
75
|
+
start(): this;
|
|
76
|
+
/**
|
|
77
|
+
* Completes the progress animation and fades out.
|
|
78
|
+
* @returns The element instance for chaining
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* Completes the progress animation and fades out.
|
|
82
|
+
* @returns The element instance for chaining
|
|
83
|
+
*/
|
|
84
|
+
finish(): this;
|
|
85
|
+
private _applyActive;
|
|
86
|
+
private _clearTimer;
|
|
87
|
+
}
|
|
88
|
+
/** The HTML tag name for the NavProgressBar custom element */
|
|
89
|
+
export declare const TAG_NAME: "vctqs1-nav-progress-bar";
|
|
90
|
+
/**
|
|
91
|
+
* Registers the NavProgressBar custom element.
|
|
92
|
+
* Requires Navigation API support; does nothing if unavailable.
|
|
93
|
+
*
|
|
94
|
+
* @param options Configuration options for all instances
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* registerNavProgressBar({ primary: '#0066cc' });
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export declare function registerNavProgressBar(options?: NavProgressBarOptions): void;
|
|
102
|
+
/**
|
|
103
|
+
* Gets the first NavProgressBar instance in the DOM.
|
|
104
|
+
* @returns The NavProgressBar element or null if not found
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* const bar = getNavProgressBar();
|
|
109
|
+
* if (bar) {
|
|
110
|
+
* bar.start();
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export declare function getNavProgressBar(): NavProgressBar | null;
|
|
115
|
+
//# sourceMappingURL=nav-progress-bar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-progress-bar.d.ts","sourceRoot":"","sources":["../../src/lib/nav-progress-bar.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAmBD;;;;;;;;;;;;;;GAcG;AACH;;;;;;;;;;;;;;GAcG;AACH,qBAAa,cAAe,SAAQ,WAAW;IAC7C,OAAO,CAAC,YAAY,CAAwC;IAC5D,OAAO,CAAC,MAAM,CAAwC;IACtD,OAAO,CAAC,cAAc,CAAwC;IAC9D,OAAO,CAAC,UAAU,CAAyD;IAC3E,OAAO,CAAC,UAAU,CAAiC;IACnD,OAAO,CAAC,QAAQ,CAAwB;IAExC,MAAM,KAAK,kBAAkB,aAE5B;IAED;;;OAGG;IACH;;;OAGG;gBACS,OAAO,GAAE,qBAA0B;IAK/C,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,WAAW,CAAsB;IAEzC,iBAAiB;IAajB,oBAAoB;IAYpB,wBAAwB;IAMxB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,gBAAgB;IA2BxB,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,OAAO;IAwCf,OAAO,CAAC,SAAS;IAMjB;;;;OAIG;IACH;;;;OAIG;IACH,KAAK,IAAI,IAAI;IAgBb;;;OAGG;IACH;;;OAGG;IACH,MAAM,IAAI,IAAI;IAmBd,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,WAAW;CAMpB;AAED,8DAA8D;AAC9D,eAAO,MAAM,QAAQ,EAAG,yBAAkC,CAAC;AAE3D;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,qBAAqB,QAgBrE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,GAAG,IAAI,CAEzD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vctqs1/nav-progress-bar",
|
|
3
|
+
"version": "0.0.2-0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./package.json": "./package.json",
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"!**/*.tsbuildinfo"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"nx": {
|
|
24
|
+
"name": "@vctqs1/nav-progress-bar",
|
|
25
|
+
"projectType": "library"
|
|
26
|
+
}
|
|
27
|
+
}
|