nanosplash 4.0.19 → 4.1.1
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/LICENSE +2 -2
- package/README.md +19 -4
- package/dist/cjs/ns.cjs.js +1 -1
- package/dist/es/ns.es.js +79 -57
- package/dist/iife/ns.iife.js +1 -1
- package/package.json +50 -45
- package/types/global.d.ts +9 -5
- package/types/interfaces/INSElement.d.ts +9 -1
- package/types/interfaces/INanosplash.d.ts +108 -48
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Copyright (c) 2025-2026 Isak Hauge
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
[](https://github.com/isakhauge/nanosplash/actions/workflows/ci.yml)
|
|
6
6
|
[](https://coveralls.io/github/isakhauge/nanosplash?branch=main)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+

|
|
9
9
|
|
|
10
10
|
```js
|
|
11
|
+
// Anti-flicker: appear only if slower than 150 ms, stay at least 400 ms
|
|
12
|
+
const ns = useNs({ showDelay: 150, minDuration: 400 })
|
|
13
|
+
|
|
11
14
|
// Fullscreen
|
|
12
15
|
ns.show('Loading')
|
|
13
16
|
|
|
@@ -16,18 +19,30 @@ ns.show('Loading', '#my-div')
|
|
|
16
19
|
|
|
17
20
|
// Hide
|
|
18
21
|
ns.hide()
|
|
22
|
+
|
|
23
|
+
// Run a job under a splash: shows before the job starts, hides when settled
|
|
24
|
+
const data = await ns.show(['Loading', () => fetchData()])
|
|
25
|
+
|
|
26
|
+
// Run labeled jobs sequentially under one splash — label updates per step,
|
|
27
|
+
// results come back as a typed tuple in order
|
|
28
|
+
const [user, posts] = await ns.show([
|
|
29
|
+
['Loading user', () => fetchUser()],
|
|
30
|
+
['Loading posts', () => fetchPosts()],
|
|
31
|
+
])
|
|
19
32
|
```
|
|
20
33
|
|
|
34
|
+
Still just two functions. Accessible out of the box (`role="status"`, `aria-live="polite"`, `aria-busy`, `prefers-reduced-motion`) and themeable via `--ns-*` CSS custom properties.
|
|
35
|
+
|
|
21
36
|
## Installation
|
|
22
|
-
|
|
37
|
+
|
|
23
38
|
```bash
|
|
24
39
|
npm install nanosplash
|
|
25
40
|
```
|
|
26
41
|
|
|
27
|
-
Or add the script tag to your HTML.
|
|
28
42
|
```html
|
|
29
43
|
<script src="https://unpkg.com/nanosplash/dist/iife/ns.iife.js"></script>
|
|
30
44
|
```
|
|
31
45
|
|
|
32
46
|
## Documentation
|
|
33
|
-
|
|
47
|
+
|
|
48
|
+
[Read the full docs here](./docs.md)
|
package/dist/cjs/ns.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=`@keyframes nsRotate{to{transform:rotate(360deg)}}@keyframes nsDash{0%{stroke-dasharray:1 150;stroke-dashoffset:0}50%{stroke-dasharray:90 150;stroke-dashoffset:-35px}to{stroke-dasharray:90 150;stroke-dashoffset:-124px}}@keyframes nsFade{0%{opacity:0}to{opacity:1}}@keyframes nsAscend{0%{opacity:0;transform:translateY(13px)}to{opacity:1;transform:translateY(0)}}:root{--ns-top:0}:where(:root){--ns-color:DarkSlateGray;--ns-size:20px;--ns-font:"Inter", "Helvetica", "Arial";--ns-weight:400;--ns-bg:#ffffffe6;--ns-z-index:10000000000;--ns-blur:blur(5px);--ns-show-delay:0s}.nsh{--color:var(--ns-color);--size:var(--ns-size);--relSize:calc(var(--size) * .9);--font:var(--ns-font);--weight:var(--ns-weight);--bg:var(--ns-bg);--zIdx:var(--ns-z-index);--blur:var(--ns-blur);z-index:var(--zIdx);position:relative}.nsh:before{animation:.2s linear backwards nsFade;animation-delay:var(--ns-show-delay);content:"";background-color:var(--bg);width:100%;height:100%;-webkit-backdrop-filter:var(--blur);z-index:calc(var(--zIdx) + 1);border-radius:inherit;position:absolute;bottom:0;right:0}body.nsh,body.nsh:before{width:100%;height:100%;top:var(--ns-top);position:fixed;left:0}.ns{width:100%;height:100%;z-index:calc(var(--zIdx) + 2);animation:2s backwards nsFade;animation-delay:var(--ns-show-delay);justify-content:center;align-items:center;gap:var(--relSize);display:flex;position:absolute;bottom:0;right:0}.nst{color:var(--color);font-size:var(--size);font-family:var(--font), sans-serif;font-weight:var(--weight);white-space:nowrap;text-overflow:ellipsis;text-shadow:0 0 .06rem #00000040;filter:drop-shadow(0 0 .07rem #00000040);max-width:80dvw;animation:.5s backwards nsAscend;animation-delay:var(--ns-show-delay);overflow:hidden}.nss{width:var(--relSize);height:var(--relSize);animation:.5s backwards nsAscend;animation-delay:var(--ns-show-delay);display:block}.nss>svg{width:inherit;height:inherit;stroke-width:8px;animation:2s linear infinite nsRotate;position:relative}.nss .path{stroke:var(--color);stroke-linecap:round;animation:1.5s ease-in-out infinite nsDash}@media (prefers-reduced-motion:reduce){.ns,.nst,.nss,.nsh:before{animation-duration:0s}.nss>svg{animation:6s linear infinite nsRotate}.nss .path{stroke-dasharray:90 150;stroke-dashoffset:-35px;animation:none}}`,t=()=>globalThis.document,n=()=>globalThis.document.body,r=e=>Array.from(e),i=(e,t)=>r(e.querySelectorAll(t)),a=(e,t)=>i(e,t)[0]??null,o=e=>e instanceof Element?e:a(t(),e),s=(e,...n)=>{let r=t().createElement(`div`);return e&&r.classList.add(e),r.append(...n),r},c=e=>{let t=s();return t.innerHTML=e,t.firstChild},l={ns:`ns`,nsHost:`nsh`,nsText:`nst`,nsSpinner:`nss`},u={ns:`.`+l.ns,nsHost:`.`+l.nsHost,nsText:`.`+l.nsText,nsSpinner:`.`+l.nsSpinner},d=`4.1.1`,f=1,p=e=>Array.isArray(e)&&typeof e[1]==`function`,m=m=>{let h=()=>i(t(),u.ns),g=()=>{let e=c(`<svg viewBox="0 0 50 50"><circle class=path cx=25 cy=25 r=20 fill=none /></svg>`);e.setAttribute(`aria-hidden`,`true`);let t=s(l.ns,s(l.nsText),s(l.nsSpinner,e));return t.setAttribute(`role`,`status`),t.setAttribute(`aria-live`,`polite`),t.nsId=f++,t},_=()=>h().filter(e=>e.nsHideTimer===void 0).sort((e,t)=>e.nsId-t.nsId),v=()=>_()[0]??null,y=(e,t)=>{if(a(e,u.nsText)?.remove(),!t)return;let n=s(l.nsText,t);e.insertBefore(n,e.firstChild)},b=(e,t)=>{let n=t.firstElementChild;n?t.insertBefore(e,n):t.append(e),t.classList.add(l.nsHost),t.setAttribute(`aria-busy`,`true`)},x=(e,t)=>{e.nsShownAt=performance.now(),e.nsShowDelay=m?.showDelay??0,e.nsMinDuration=m?.minDuration??0,t.style.setProperty(`--ns-show-delay`,e.nsShowDelay+`ms`)},S=e=>r(e.children).find(e=>e.classList.contains(l.ns)),C=(e,t)=>{let r=t?o(t):n();if(!r)return null;k();let i,a=S(r);if(a?(i=a,clearTimeout(i.nsHideTimer),i.nsHideTimer=void 0):(i=g(),b(i,r)),x(i,r),y(i,e??``),r===n()){let e=scrollY+`px`;n().style.setProperty(`--ns-top`,e)}return i.nsId},w=e=>{let t=e.parentElement;t&&(t.classList.remove(l.nsHost),t.removeAttribute(`aria-busy`),t.style.removeProperty(`--ns-show-delay`)),e.remove()},T=e=>{if(!e||e.nsHideTimer!==void 0)return;let t=e.nsShowDelay??0,n=performance.now()-((e.nsShownAt??0)+t),r=t>0&&n<0,i=(e.nsMinDuration??0)-n;if(!r&&i>0){e.nsHideTimer=setTimeout(()=>w(e),i);return}w(e)},E=e=>h().find(t=>t.nsId===e)??null,D=e=>{e===`*`?h().forEach(T):T(typeof e==`number`?E(e):v())},O=()=>c(`<style id="ns">${e}</style>`),k=()=>{let e=a(t(),`#ns`),n=O();if(!e){t().head.append(n);return}e.textContent!==n.textContent&&e.replaceWith(n)},A=async(e,t)=>{if(e.length===0)return[];let n=[],r=null;try{for(let[i,a]of e)r=C(i,t),n.push(await a())}finally{r!==null&&D(r)}return n};return{show:((e,t)=>p(e)?A([e],t).then(e=>e[0]):Array.isArray(e)?A(e,t):C(e,t)),hide:D,version:d}};exports.useNs=m;
|
package/dist/es/ns.es.js
CHANGED
|
@@ -1,58 +1,80 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
1
|
+
//#region src/style/ns.css?inline
|
|
2
|
+
var e = () => globalThis.document, t = () => globalThis.document.body, n = (e) => Array.from(e), r = (e, t) => n(e.querySelectorAll(t)), i = (e, t) => r(e, t)[0] ?? null, a = (t) => t instanceof Element ? t : i(e(), t), o = (t, ...n) => {
|
|
3
|
+
let r = e().createElement("div");
|
|
4
|
+
return t && r.classList.add(t), r.append(...n), r;
|
|
5
|
+
}, s = (e) => {
|
|
6
|
+
let t = o();
|
|
7
|
+
return t.innerHTML = e, t.firstChild;
|
|
8
|
+
}, c = {
|
|
9
|
+
ns: "ns",
|
|
10
|
+
nsHost: "nsh",
|
|
11
|
+
nsText: "nst",
|
|
12
|
+
nsSpinner: "nss"
|
|
13
|
+
}, l = {
|
|
14
|
+
ns: "." + c.ns,
|
|
15
|
+
nsHost: "." + c.nsHost,
|
|
16
|
+
nsText: "." + c.nsText,
|
|
17
|
+
nsSpinner: "." + c.nsSpinner
|
|
18
|
+
}, u = "4.1.1", d = 1, f = (e) => Array.isArray(e) && typeof e[1] == "function", p = (p) => {
|
|
19
|
+
let m = () => r(e(), l.ns), h = () => {
|
|
20
|
+
let e = s("<svg viewBox=\"0 0 50 50\"><circle class=path cx=25 cy=25 r=20 fill=none /></svg>");
|
|
21
|
+
e.setAttribute("aria-hidden", "true");
|
|
22
|
+
let t = o(c.ns, o(c.nsText), o(c.nsSpinner, e));
|
|
23
|
+
return t.setAttribute("role", "status"), t.setAttribute("aria-live", "polite"), t.nsId = d++, t;
|
|
24
|
+
}, g = () => m().filter((e) => e.nsHideTimer === void 0).sort((e, t) => e.nsId - t.nsId), _ = () => g()[0] ?? null, v = (e, t) => {
|
|
25
|
+
if (i(e, l.nsText)?.remove(), !t) return;
|
|
26
|
+
let n = o(c.nsText, t);
|
|
27
|
+
e.insertBefore(n, e.firstChild);
|
|
28
|
+
}, y = (e, t) => {
|
|
29
|
+
let n = t.firstElementChild;
|
|
30
|
+
n ? t.insertBefore(e, n) : t.append(e), t.classList.add(c.nsHost), t.setAttribute("aria-busy", "true");
|
|
31
|
+
}, b = (e, t) => {
|
|
32
|
+
e.nsShownAt = performance.now(), e.nsShowDelay = p?.showDelay ?? 0, e.nsMinDuration = p?.minDuration ?? 0, t.style.setProperty("--ns-show-delay", e.nsShowDelay + "ms");
|
|
33
|
+
}, x = (e) => n(e.children).find((e) => e.classList.contains(c.ns)), S = (e, n) => {
|
|
34
|
+
let r = n ? a(n) : t();
|
|
35
|
+
if (!r) return null;
|
|
36
|
+
O();
|
|
37
|
+
let i, o = x(r);
|
|
38
|
+
if (o ? (i = o, clearTimeout(i.nsHideTimer), i.nsHideTimer = void 0) : (i = h(), y(i, r)), b(i, r), v(i, e ?? ""), r === t()) {
|
|
39
|
+
let e = scrollY + "px";
|
|
40
|
+
t().style.setProperty("--ns-top", e);
|
|
41
|
+
}
|
|
42
|
+
return i.nsId;
|
|
43
|
+
}, C = (e) => {
|
|
44
|
+
let t = e.parentElement;
|
|
45
|
+
t && (t.classList.remove(c.nsHost), t.removeAttribute("aria-busy"), t.style.removeProperty("--ns-show-delay")), e.remove();
|
|
46
|
+
}, w = (e) => {
|
|
47
|
+
if (!e || e.nsHideTimer !== void 0) return;
|
|
48
|
+
let t = e.nsShowDelay ?? 0, n = performance.now() - ((e.nsShownAt ?? 0) + t), r = t > 0 && n < 0, i = (e.nsMinDuration ?? 0) - n;
|
|
49
|
+
if (!r && i > 0) {
|
|
50
|
+
e.nsHideTimer = setTimeout(() => C(e), i);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
C(e);
|
|
54
|
+
}, T = (e) => m().find((t) => t.nsId === e) ?? null, E = (e) => {
|
|
55
|
+
e === "*" ? m().forEach(w) : w(typeof e == "number" ? T(e) : _());
|
|
56
|
+
}, D = () => s("<style id=\"ns\">@keyframes nsRotate{to{transform:rotate(360deg)}}@keyframes nsDash{0%{stroke-dasharray:1 150;stroke-dashoffset:0}50%{stroke-dasharray:90 150;stroke-dashoffset:-35px}to{stroke-dasharray:90 150;stroke-dashoffset:-124px}}@keyframes nsFade{0%{opacity:0}to{opacity:1}}@keyframes nsAscend{0%{opacity:0;transform:translateY(13px)}to{opacity:1;transform:translateY(0)}}:root{--ns-top:0}:where(:root){--ns-color:DarkSlateGray;--ns-size:20px;--ns-font:\"Inter\", \"Helvetica\", \"Arial\";--ns-weight:400;--ns-bg:#ffffffe6;--ns-z-index:10000000000;--ns-blur:blur(5px);--ns-show-delay:0s}.nsh{--color:var(--ns-color);--size:var(--ns-size);--relSize:calc(var(--size) * .9);--font:var(--ns-font);--weight:var(--ns-weight);--bg:var(--ns-bg);--zIdx:var(--ns-z-index);--blur:var(--ns-blur);z-index:var(--zIdx);position:relative}.nsh:before{animation:.2s linear backwards nsFade;animation-delay:var(--ns-show-delay);content:\"\";background-color:var(--bg);width:100%;height:100%;-webkit-backdrop-filter:var(--blur);z-index:calc(var(--zIdx) + 1);border-radius:inherit;position:absolute;bottom:0;right:0}body.nsh,body.nsh:before{width:100%;height:100%;top:var(--ns-top);position:fixed;left:0}.ns{width:100%;height:100%;z-index:calc(var(--zIdx) + 2);animation:2s backwards nsFade;animation-delay:var(--ns-show-delay);justify-content:center;align-items:center;gap:var(--relSize);display:flex;position:absolute;bottom:0;right:0}.nst{color:var(--color);font-size:var(--size);font-family:var(--font), sans-serif;font-weight:var(--weight);white-space:nowrap;text-overflow:ellipsis;text-shadow:0 0 .06rem #00000040;filter:drop-shadow(0 0 .07rem #00000040);max-width:80dvw;animation:.5s backwards nsAscend;animation-delay:var(--ns-show-delay);overflow:hidden}.nss{width:var(--relSize);height:var(--relSize);animation:.5s backwards nsAscend;animation-delay:var(--ns-show-delay);display:block}.nss>svg{width:inherit;height:inherit;stroke-width:8px;animation:2s linear infinite nsRotate;position:relative}.nss .path{stroke:var(--color);stroke-linecap:round;animation:1.5s ease-in-out infinite nsDash}@media (prefers-reduced-motion:reduce){.ns,.nst,.nss,.nsh:before{animation-duration:0s}.nss>svg{animation:6s linear infinite nsRotate}.nss .path{stroke-dasharray:90 150;stroke-dashoffset:-35px;animation:none}}</style>"), O = () => {
|
|
57
|
+
let t = i(e(), "#ns"), n = D();
|
|
58
|
+
if (!t) {
|
|
59
|
+
e().head.append(n);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
t.textContent !== n.textContent && t.replaceWith(n);
|
|
63
|
+
}, k = async (e, t) => {
|
|
64
|
+
if (e.length === 0) return [];
|
|
65
|
+
let n = [], r = null;
|
|
66
|
+
try {
|
|
67
|
+
for (let [i, a] of e) r = S(i, t), n.push(await a());
|
|
68
|
+
} finally {
|
|
69
|
+
r !== null && E(r);
|
|
70
|
+
}
|
|
71
|
+
return n;
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
show: ((e, t) => f(e) ? k([e], t).then((e) => e[0]) : Array.isArray(e) ? k(e, t) : S(e, t)),
|
|
75
|
+
hide: E,
|
|
76
|
+
version: u
|
|
77
|
+
};
|
|
58
78
|
};
|
|
79
|
+
//#endregion
|
|
80
|
+
export { p as useNs };
|
package/dist/iife/ns.iife.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(){"
|
|
1
|
+
(function(){var e=()=>globalThis.document,t=()=>globalThis.document.body,n=e=>Array.from(e),r=(e,t)=>n(e.querySelectorAll(t)),i=(e,t)=>r(e,t)[0]??null,a=t=>t instanceof Element?t:i(e(),t),o=(t,...n)=>{let r=e().createElement(`div`);return t&&r.classList.add(t),r.append(...n),r},s=e=>{let t=o();return t.innerHTML=e,t.firstChild},c={ns:`ns`,nsHost:`nsh`,nsText:`nst`,nsSpinner:`nss`},l={ns:`.`+c.ns,nsHost:`.`+c.nsHost,nsText:`.`+c.nsText,nsSpinner:`.`+c.nsSpinner},u=`4.1.1`,d=1,f=e=>Array.isArray(e)&&typeof e[1]==`function`,p=p=>{let m=()=>r(e(),l.ns),h=()=>{let e=s(`<svg viewBox="0 0 50 50"><circle class=path cx=25 cy=25 r=20 fill=none /></svg>`);e.setAttribute(`aria-hidden`,`true`);let t=o(c.ns,o(c.nsText),o(c.nsSpinner,e));return t.setAttribute(`role`,`status`),t.setAttribute(`aria-live`,`polite`),t.nsId=d++,t},g=()=>m().filter(e=>e.nsHideTimer===void 0).sort((e,t)=>e.nsId-t.nsId),_=()=>g()[0]??null,v=(e,t)=>{if(i(e,l.nsText)?.remove(),!t)return;let n=o(c.nsText,t);e.insertBefore(n,e.firstChild)},y=(e,t)=>{let n=t.firstElementChild;n?t.insertBefore(e,n):t.append(e),t.classList.add(c.nsHost),t.setAttribute(`aria-busy`,`true`)},b=(e,t)=>{e.nsShownAt=performance.now(),e.nsShowDelay=p?.showDelay??0,e.nsMinDuration=p?.minDuration??0,t.style.setProperty(`--ns-show-delay`,e.nsShowDelay+`ms`)},x=e=>n(e.children).find(e=>e.classList.contains(c.ns)),S=(e,n)=>{let r=n?a(n):t();if(!r)return null;O();let i,o=x(r);if(o?(i=o,clearTimeout(i.nsHideTimer),i.nsHideTimer=void 0):(i=h(),y(i,r)),b(i,r),v(i,e??``),r===t()){let e=scrollY+`px`;t().style.setProperty(`--ns-top`,e)}return i.nsId},C=e=>{let t=e.parentElement;t&&(t.classList.remove(c.nsHost),t.removeAttribute(`aria-busy`),t.style.removeProperty(`--ns-show-delay`)),e.remove()},w=e=>{if(!e||e.nsHideTimer!==void 0)return;let t=e.nsShowDelay??0,n=performance.now()-((e.nsShownAt??0)+t),r=t>0&&n<0,i=(e.nsMinDuration??0)-n;if(!r&&i>0){e.nsHideTimer=setTimeout(()=>C(e),i);return}C(e)},T=e=>m().find(t=>t.nsId===e)??null,E=e=>{e===`*`?m().forEach(w):w(typeof e==`number`?T(e):_())},D=()=>s(`<style id="ns">@keyframes nsRotate{to{transform:rotate(360deg)}}@keyframes nsDash{0%{stroke-dasharray:1 150;stroke-dashoffset:0}50%{stroke-dasharray:90 150;stroke-dashoffset:-35px}to{stroke-dasharray:90 150;stroke-dashoffset:-124px}}@keyframes nsFade{0%{opacity:0}to{opacity:1}}@keyframes nsAscend{0%{opacity:0;transform:translateY(13px)}to{opacity:1;transform:translateY(0)}}:root{--ns-top:0}:where(:root){--ns-color:DarkSlateGray;--ns-size:20px;--ns-font:"Inter", "Helvetica", "Arial";--ns-weight:400;--ns-bg:#ffffffe6;--ns-z-index:10000000000;--ns-blur:blur(5px);--ns-show-delay:0s}.nsh{--color:var(--ns-color);--size:var(--ns-size);--relSize:calc(var(--size) * .9);--font:var(--ns-font);--weight:var(--ns-weight);--bg:var(--ns-bg);--zIdx:var(--ns-z-index);--blur:var(--ns-blur);z-index:var(--zIdx);position:relative}.nsh:before{animation:.2s linear backwards nsFade;animation-delay:var(--ns-show-delay);content:"";background-color:var(--bg);width:100%;height:100%;-webkit-backdrop-filter:var(--blur);z-index:calc(var(--zIdx) + 1);border-radius:inherit;position:absolute;bottom:0;right:0}body.nsh,body.nsh:before{width:100%;height:100%;top:var(--ns-top);position:fixed;left:0}.ns{width:100%;height:100%;z-index:calc(var(--zIdx) + 2);animation:2s backwards nsFade;animation-delay:var(--ns-show-delay);justify-content:center;align-items:center;gap:var(--relSize);display:flex;position:absolute;bottom:0;right:0}.nst{color:var(--color);font-size:var(--size);font-family:var(--font), sans-serif;font-weight:var(--weight);white-space:nowrap;text-overflow:ellipsis;text-shadow:0 0 .06rem #00000040;filter:drop-shadow(0 0 .07rem #00000040);max-width:80dvw;animation:.5s backwards nsAscend;animation-delay:var(--ns-show-delay);overflow:hidden}.nss{width:var(--relSize);height:var(--relSize);animation:.5s backwards nsAscend;animation-delay:var(--ns-show-delay);display:block}.nss>svg{width:inherit;height:inherit;stroke-width:8px;animation:2s linear infinite nsRotate;position:relative}.nss .path{stroke:var(--color);stroke-linecap:round;animation:1.5s ease-in-out infinite nsDash}@media (prefers-reduced-motion:reduce){.ns,.nst,.nss,.nsh:before{animation-duration:0s}.nss>svg{animation:6s linear infinite nsRotate}.nss .path{stroke-dasharray:90 150;stroke-dashoffset:-35px;animation:none}}</style>`),O=()=>{let t=i(e(),`#ns`),n=D();if(!t){e().head.append(n);return}t.textContent!==n.textContent&&t.replaceWith(n)},k=async(e,t)=>{if(e.length===0)return[];let n=[],r=null;try{for(let[i,a]of e)r=S(i,t),n.push(await a())}finally{r!==null&&E(r)}return n};return{show:((e,t)=>f(e)?k([e],t).then(e=>e[0]):Array.isArray(e)?k(e,t):S(e,t)),hide:E,version:u}};window.addEventListener(`load`,()=>{window.ns=p()})})();
|
package/package.json
CHANGED
|
@@ -1,55 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nanosplash",
|
|
3
|
+
"version": "4.1.1",
|
|
3
4
|
"private": false,
|
|
4
|
-
"version": "4.0.19",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"author": {
|
|
7
|
-
"name": "Isak K. Hauge",
|
|
8
|
-
"email": "isakhauge@icloud.com",
|
|
9
|
-
"url": "https://no.linkedin.com/in/isakhauge"
|
|
10
|
-
},
|
|
11
|
-
"repository": {
|
|
12
|
-
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/isakhauge/nanosplash.git"
|
|
14
|
-
},
|
|
15
5
|
"description": "The tiny loading screen for web artisans",
|
|
16
|
-
"homepage": "https://github.com/isakhauge/nanosplash",
|
|
17
6
|
"keywords": [
|
|
18
|
-
"
|
|
7
|
+
"cdn",
|
|
8
|
+
"es",
|
|
9
|
+
"esm",
|
|
10
|
+
"iife",
|
|
19
11
|
"loader",
|
|
20
|
-
"
|
|
21
|
-
"splash",
|
|
12
|
+
"loading",
|
|
22
13
|
"nano",
|
|
23
14
|
"nanosplash",
|
|
15
|
+
"screen",
|
|
24
16
|
"small",
|
|
17
|
+
"splash",
|
|
25
18
|
"tiny",
|
|
26
|
-
"es",
|
|
27
|
-
"esm",
|
|
28
19
|
"ts",
|
|
29
20
|
"typescript",
|
|
30
|
-
"iife",
|
|
31
|
-
"cdn",
|
|
32
21
|
"vite"
|
|
33
22
|
],
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"build:iife": "tsc && cross-env entry=./src/iife.ts format=iife vite build -m production --outDir ./dist/iife",
|
|
40
|
-
"preview": "vite preview",
|
|
41
|
-
"lint": "eslint . --fix",
|
|
42
|
-
"format": "prettier --write ./src/**/*.ts ./vite.config.ts ./vitest.config.ts",
|
|
43
|
-
"test": "bunx vitest --coverage"
|
|
23
|
+
"homepage": "https://github.com/isakhauge/nanosplash",
|
|
24
|
+
"author": {
|
|
25
|
+
"name": "Isak K. Hauge",
|
|
26
|
+
"email": "isakhauge@icloud.com",
|
|
27
|
+
"url": "https://no.linkedin.com/in/isakhauge"
|
|
44
28
|
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/isakhauge/nanosplash.git"
|
|
32
|
+
},
|
|
33
|
+
"source": "./src/composables/ns.ts",
|
|
45
34
|
"files": [
|
|
46
35
|
"dist",
|
|
47
36
|
"types",
|
|
48
37
|
"README.md",
|
|
49
38
|
"LICENSE"
|
|
50
39
|
],
|
|
51
|
-
"
|
|
40
|
+
"type": "module",
|
|
52
41
|
"main": "./dist/es/ns.es.js",
|
|
42
|
+
"types": "./types/global.d.ts",
|
|
43
|
+
"unpkg": "./dist/iife/ns.iife.js",
|
|
44
|
+
"jsdelivr": "./dist/iife/ns.iife.js",
|
|
53
45
|
"exports": {
|
|
54
46
|
".": {
|
|
55
47
|
"types": "./types/global.d.ts",
|
|
@@ -58,27 +50,40 @@
|
|
|
58
50
|
"default": "./dist/es/ns.es.js"
|
|
59
51
|
}
|
|
60
52
|
},
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
53
|
+
"scripts": {
|
|
54
|
+
"dev": "vp dev",
|
|
55
|
+
"build": "bun run build:es && bun run build:cjs && bun run build:iife",
|
|
56
|
+
"build:es": "tsc && cross-env entry=./src/composables/ns.ts format=es vp build -m production --outDir ./dist/es",
|
|
57
|
+
"build:cjs": "tsc && cross-env entry=./src/composables/ns.ts format=cjs vp build -m production --outDir ./dist/cjs",
|
|
58
|
+
"build:iife": "tsc && cross-env entry=./src/iife.ts format=iife vp build -m production --outDir ./dist/iife",
|
|
59
|
+
"preview": "vp preview",
|
|
60
|
+
"lint": "vp lint",
|
|
61
|
+
"format": "vp fmt",
|
|
62
|
+
"test": "vp test --coverage",
|
|
63
|
+
"check": "vp check"
|
|
64
|
+
},
|
|
64
65
|
"devDependencies": {
|
|
65
|
-
"@eslint/js": "^10.0.1",
|
|
66
66
|
"@types/node": "^25.3.0",
|
|
67
|
-
"@
|
|
68
|
-
"@typescript-eslint/parser": "^8.56.0",
|
|
69
|
-
"@vitest/coverage-v8": "4.0.18",
|
|
67
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
70
68
|
"cross-env": "^10.1.0",
|
|
71
69
|
"cssnano": "^7.1.2",
|
|
72
70
|
"dotenv": "^17.3.1",
|
|
73
|
-
"eslint": "^10.0.1",
|
|
74
|
-
"eslint-config-prettier": "^10.1.8",
|
|
75
|
-
"globals": "^17.3.0",
|
|
76
71
|
"jsdom": "^28.1.0",
|
|
77
72
|
"postcss": "^8.5.6",
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
|
|
73
|
+
"typescript": "7.0.2",
|
|
74
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
|
|
75
|
+
"vite-plus": "0.3.0",
|
|
76
|
+
"vitest": "4.1.11"
|
|
77
|
+
},
|
|
78
|
+
"overrides": {
|
|
79
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
|
|
80
|
+
"vitest": "4.1.11"
|
|
81
|
+
},
|
|
82
|
+
"devEngines": {
|
|
83
|
+
"packageManager": {
|
|
84
|
+
"name": "bun",
|
|
85
|
+
"version": "1.4.0",
|
|
86
|
+
"onFail": "warn"
|
|
87
|
+
}
|
|
83
88
|
}
|
|
84
89
|
}
|
package/types/global.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { INanosplash } from './interfaces/INanosplash.ts'
|
|
1
|
+
import type { INanosplash, NsOptions } from './interfaces/INanosplash.ts'
|
|
2
2
|
|
|
3
3
|
declare global {
|
|
4
4
|
interface Window {
|
|
@@ -11,9 +11,11 @@ declare global {
|
|
|
11
11
|
*
|
|
12
12
|
* **The tiny loading screen for web artisans**
|
|
13
13
|
*
|
|
14
|
-
* `useNs` is the
|
|
15
|
-
*
|
|
16
|
-
* We recommend
|
|
14
|
+
* Create the Nanosplash API. `useNs` is the sole entry point; styles inject
|
|
15
|
+
* lazily on the first `show()`.
|
|
16
|
+
* We recommend adding Nanosplash to the Window.
|
|
17
|
+
* @param options Anti-flicker timing applied to every splash shown through
|
|
18
|
+
* this instance.
|
|
17
19
|
* @returns Instance of Nanosplash API
|
|
18
20
|
* @example The short and easy way.
|
|
19
21
|
* window.ns = useNs()
|
|
@@ -21,6 +23,8 @@ declare global {
|
|
|
21
23
|
* window.addEventListener('load', function() {
|
|
22
24
|
* this.ns = useNs()
|
|
23
25
|
* })
|
|
26
|
+
* @example With anti-flicker timing.
|
|
27
|
+
* const ns = useNs({ showDelay: 150, minDuration: 400 })
|
|
24
28
|
* @author Isak Hauge <https://www.linkedin.com/in/isakhauge/>
|
|
25
29
|
*/
|
|
26
|
-
export const useNs: () => INanosplash
|
|
30
|
+
export const useNs: (options?: NsOptions) => INanosplash
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import type { int } from
|
|
1
|
+
import type { int } from '../semantic/number'
|
|
2
2
|
|
|
3
3
|
export interface INSElement extends HTMLDivElement {
|
|
4
4
|
nsId: int
|
|
5
|
+
/** `performance.now()` timestamp of the last `show()` call targeting this element. */
|
|
6
|
+
nsShownAt?: number
|
|
7
|
+
/** Milliseconds the splash stays invisible after `show()` (anti-flicker). */
|
|
8
|
+
nsShowDelay?: int
|
|
9
|
+
/** Minimum milliseconds the splash stays visible once shown (anti-flicker). */
|
|
10
|
+
nsMinDuration?: int
|
|
11
|
+
/** Pending deferred-removal timer, set while a `hide()` waits out `minDuration`. */
|
|
12
|
+
nsHideTimer?: ReturnType<typeof setTimeout>
|
|
5
13
|
}
|
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
import type { ElementRef } from '../dom'
|
|
2
2
|
import type { int } from '../semantic/number'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* # Options
|
|
6
|
+
* Configuration for `useNs()`. Applies to every splash shown through the
|
|
7
|
+
* returned API instance, keeping visual behavior consistent.
|
|
8
|
+
*/
|
|
9
|
+
export interface NsOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Milliseconds to keep the splash invisible after `show()`.
|
|
12
|
+
* If it is hidden before the delay elapses, it never becomes visible.
|
|
13
|
+
* Prevents a flash of spinner on fast operations.
|
|
14
|
+
*/
|
|
15
|
+
showDelay?: int
|
|
16
|
+
/**
|
|
17
|
+
* Minimum milliseconds the splash stays visible once shown.
|
|
18
|
+
* A `hide()` call arriving earlier is deferred until the minimum has passed.
|
|
19
|
+
* Prevents a barely-visible blink.
|
|
20
|
+
*/
|
|
21
|
+
minDuration?: int
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* # Job
|
|
26
|
+
* An async unit of work tracked by a Nanosplash. The splash is guaranteed
|
|
27
|
+
* to be visible before the job is invoked.
|
|
28
|
+
*/
|
|
29
|
+
export type NsJob<T> = () => Promise<T>
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* # Label
|
|
33
|
+
* The text displayed beside the spinner, or falsy for spinner only.
|
|
34
|
+
*/
|
|
35
|
+
export type NsLabel = string | null | undefined
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* # Labeled job
|
|
39
|
+
* A job with a label. While the job runs, the splash shows the label.
|
|
40
|
+
*/
|
|
41
|
+
export type NsLabeledJob<T> = [NsLabel, NsJob<T>]
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* # Show input
|
|
45
|
+
* Everything `show()` accepts as its first argument.
|
|
46
|
+
*/
|
|
47
|
+
export type NsShowInput =
|
|
48
|
+
| NsLabel
|
|
49
|
+
| NsLabeledJob<unknown>
|
|
50
|
+
| readonly NsLabeledJob<unknown>[]
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* # Job results
|
|
54
|
+
* Maps a tuple of labeled jobs to a tuple of each job's resolved return value.
|
|
55
|
+
*/
|
|
56
|
+
export type NsJobResults<Jobs extends readonly NsLabeledJob<unknown>[]> = {
|
|
57
|
+
-readonly [K in keyof Jobs]: Jobs[K] extends NsLabeledJob<infer R>
|
|
58
|
+
? Awaited<R>
|
|
59
|
+
: never
|
|
60
|
+
}
|
|
61
|
+
|
|
4
62
|
/**
|
|
5
63
|
* # Nanosplash
|
|
6
64
|
* @author Isak Hauge <isakhauge@icloud.com>
|
|
@@ -8,61 +66,63 @@ import type { int } from '../semantic/number'
|
|
|
8
66
|
export interface INanosplash {
|
|
9
67
|
/**
|
|
10
68
|
* # Show
|
|
11
|
-
* Display
|
|
12
|
-
* @param
|
|
13
|
-
* @param
|
|
14
|
-
*
|
|
69
|
+
* Display a splash, globally or inside a container.
|
|
70
|
+
* @param label Optional label. Pass falsy to display the spinner only.
|
|
71
|
+
* @param inside Optional container: an `Element` or a CSS selector.
|
|
72
|
+
* Omit to target the document body.
|
|
73
|
+
* @returns The splash's ID, or `null` if `inside` could not be resolved.
|
|
15
74
|
* @example
|
|
16
|
-
* //
|
|
17
|
-
* ns.show()
|
|
18
|
-
*
|
|
19
|
-
* //
|
|
20
|
-
|
|
75
|
+
* ns.show() // spinner only
|
|
76
|
+
* ns.show('Loading…') // spinner and label
|
|
77
|
+
* ns.show('Loading…', '#my-div') // inside a container (selector)
|
|
78
|
+
* ns.show(null, myElement) // inside a container (element), no label
|
|
79
|
+
*/
|
|
80
|
+
show(label?: NsLabel, inside?: ElementRef | HTMLElement): int | null
|
|
81
|
+
/**
|
|
82
|
+
* # Show (single job)
|
|
83
|
+
* Display a splash for the lifetime of one job: show it with the label
|
|
84
|
+
* before the job starts and hide it when the job settles (resolve or
|
|
85
|
+
* reject). Pass the job's result through.
|
|
86
|
+
* @param job A `[label, job]` pair.
|
|
87
|
+
* @param inside Optional container: an `Element` or a CSS selector.
|
|
88
|
+
* @returns The job's resolved value.
|
|
21
89
|
* @example
|
|
22
|
-
*
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
90
|
+
* const user = await ns.show(['Loading user…', () => fetchUser()])
|
|
91
|
+
*/
|
|
92
|
+
show<T>(
|
|
93
|
+
job: NsLabeledJob<T>,
|
|
94
|
+
inside?: ElementRef | HTMLElement,
|
|
95
|
+
): Promise<Awaited<T>>
|
|
96
|
+
/**
|
|
97
|
+
* # Show (job sequence)
|
|
98
|
+
* Display one splash across sequential labeled jobs, updating the label as
|
|
99
|
+
* each job starts. Resolve with the jobs' results as a typed tuple, in
|
|
100
|
+
* input order. Fail fast: the first rejection hides the splash,
|
|
101
|
+
* propagates, and skips the remaining jobs.
|
|
102
|
+
* @param jobs An array of `[label, job]` pairs.
|
|
103
|
+
* @param inside Optional container: an `Element` or a CSS selector.
|
|
104
|
+
* @returns The jobs' resolved values, in input order.
|
|
32
105
|
* @example
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* // Alternative 1: Using node
|
|
39
|
-
* ns.show(null, div)
|
|
40
|
-
*
|
|
41
|
-
* // Alternative 2: Using selector
|
|
42
|
-
* ns.show(null, '#my-div')
|
|
106
|
+
* const [user, posts] = await ns.show([
|
|
107
|
+
* ['Loading user…', () => fetchUser()],
|
|
108
|
+
* ['Loading posts…', () => fetchPosts()],
|
|
109
|
+
* ])
|
|
43
110
|
*/
|
|
44
|
-
show(
|
|
45
|
-
|
|
46
|
-
inside?: ElementRef | HTMLElement
|
|
47
|
-
):
|
|
111
|
+
show<Jobs extends readonly NsLabeledJob<unknown>[]>(
|
|
112
|
+
jobs: readonly [...Jobs],
|
|
113
|
+
inside?: ElementRef | HTMLElement,
|
|
114
|
+
): Promise<NsJobResults<Jobs>>
|
|
48
115
|
/**
|
|
49
116
|
* # Hide
|
|
50
|
-
* Remove
|
|
51
|
-
* - Pass ID:
|
|
52
|
-
* - Pass '*'
|
|
53
|
-
* - Pass nothing:
|
|
54
|
-
* @param id
|
|
55
|
-
* @example
|
|
56
|
-
* // Remove the first Nanosplash in the queue (FIFO)
|
|
57
|
-
* ns.hide()
|
|
58
|
-
* @example
|
|
59
|
-
* // Remove specific Nanosplash in case you have multiple
|
|
60
|
-
* const idA: number = ns.show(null, '#div-a') // 1700000000000
|
|
61
|
-
* const idB: number = ns.show(null, '#div-b') // 1800000000000
|
|
62
|
-
* ns.hide(idB) // Only hides Nanosplash inside the element with ID div-b
|
|
117
|
+
* Remove one or more splashes.
|
|
118
|
+
* - Pass an ID: remove that splash.
|
|
119
|
+
* - Pass `'*'`: remove every splash.
|
|
120
|
+
* - Pass nothing: remove the oldest splash (FIFO).
|
|
121
|
+
* @param id A splash ID, `'*'`, or nothing.
|
|
63
122
|
* @example
|
|
64
|
-
* //
|
|
65
|
-
* ns.hide(
|
|
123
|
+
* ns.hide() // oldest splash
|
|
124
|
+
* ns.hide(id) // specific splash, using the ID returned by show()
|
|
125
|
+
* ns.hide('*') // every splash
|
|
66
126
|
*/
|
|
67
127
|
hide(id?: int | '*'): void
|
|
68
128
|
/**
|