unicode-animations 0.1.3 → 0.1.9
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 +100 -12
- package/package.json +5 -2
- package/scripts/demo.cjs +75 -0
- package/scripts/postinstall.cjs +117 -0
- package/scripts/postinstall.js +0 -67
package/README.md
CHANGED
|
@@ -30,7 +30,9 @@ const spinners = require('unicode-animations');
|
|
|
30
30
|
|
|
31
31
|
Each spinner is a `{ frames: string[], interval: number }` object.
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## Examples
|
|
34
|
+
|
|
35
|
+
### Spinner while awaiting an async task
|
|
34
36
|
|
|
35
37
|
```js
|
|
36
38
|
import spinners from 'unicode-animations';
|
|
@@ -38,29 +40,115 @@ import spinners from 'unicode-animations';
|
|
|
38
40
|
const { frames, interval } = spinners.braille;
|
|
39
41
|
let i = 0;
|
|
40
42
|
|
|
41
|
-
const
|
|
42
|
-
process.stdout.write(`\r${frames[i++ % frames.length]}
|
|
43
|
+
const spinner = setInterval(() => {
|
|
44
|
+
process.stdout.write(`\r ${frames[i++ % frames.length]} Installing dependencies...`);
|
|
43
45
|
}, interval);
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}, 3000);
|
|
47
|
+
await install();
|
|
48
|
+
|
|
49
|
+
clearInterval(spinner);
|
|
50
|
+
process.stdout.write('\r ✔ Installed successfully.\n');
|
|
50
51
|
```
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
### Multi-character braille spinner
|
|
54
|
+
|
|
55
|
+
The grid-based spinners produce wider animated patterns — useful for visual flair in CLI tools:
|
|
53
56
|
|
|
54
57
|
```js
|
|
55
58
|
import spinners from 'unicode-animations';
|
|
56
59
|
|
|
57
|
-
const
|
|
58
|
-
|
|
60
|
+
const { frames, interval } = spinners.helix;
|
|
61
|
+
let i = 0;
|
|
62
|
+
|
|
63
|
+
const spinner = setInterval(() => {
|
|
64
|
+
process.stdout.write(`\r ${frames[i++ % frames.length]} Building...`);
|
|
65
|
+
}, interval);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Progress indicator with dynamic message
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
import spinners from 'unicode-animations';
|
|
72
|
+
|
|
73
|
+
function withSpinner(message, spinner = spinners.braille) {
|
|
74
|
+
let i = 0;
|
|
75
|
+
const { frames, interval } = spinner;
|
|
76
|
+
const timer = setInterval(() => {
|
|
77
|
+
process.stdout.write(`\r\x1B[2K ${frames[i++ % frames.length]} ${message}`);
|
|
78
|
+
}, interval);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
update(msg) { message = msg; },
|
|
82
|
+
stop(finalMsg) {
|
|
83
|
+
clearInterval(timer);
|
|
84
|
+
process.stdout.write(`\r\x1B[2K ✔ ${finalMsg}\n`);
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const spin = withSpinner('Fetching data...');
|
|
90
|
+
const data = await fetchData();
|
|
91
|
+
spin.update(`Processing ${data.length} records...`);
|
|
92
|
+
await processData(data);
|
|
93
|
+
spin.stop('Done.');
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Cycle through different spinners
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
import spinners from 'unicode-animations';
|
|
100
|
+
|
|
101
|
+
const names = ['scan', 'rain', 'helix', 'cascade'];
|
|
102
|
+
let current = 0;
|
|
59
103
|
let i = 0;
|
|
60
104
|
|
|
61
105
|
setInterval(() => {
|
|
62
|
-
|
|
106
|
+
const s = spinners[names[current]];
|
|
107
|
+
process.stdout.write(`\r\x1B[2K ${s.frames[i % s.frames.length]} ${names[current]}`);
|
|
108
|
+
i++;
|
|
109
|
+
if (i % 20 === 0) current = (current + 1) % names.length;
|
|
110
|
+
}, 80);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Browser — inline loading text
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
import spinners from 'unicode-animations';
|
|
117
|
+
|
|
118
|
+
const el = document.getElementById('status');
|
|
119
|
+
const { frames, interval } = spinners.arc;
|
|
120
|
+
let i = 0;
|
|
121
|
+
|
|
122
|
+
const spinner = setInterval(() => {
|
|
123
|
+
el.textContent = `${frames[i++ % frames.length]} Loading...`;
|
|
63
124
|
}, interval);
|
|
125
|
+
|
|
126
|
+
// Stop when done
|
|
127
|
+
function onLoaded() {
|
|
128
|
+
clearInterval(spinner);
|
|
129
|
+
el.textContent = '✔ Ready';
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### React component
|
|
134
|
+
|
|
135
|
+
```jsx
|
|
136
|
+
import { useState, useEffect } from 'react';
|
|
137
|
+
import spinners from 'unicode-animations';
|
|
138
|
+
|
|
139
|
+
function Spinner({ name = 'braille', text = 'Loading...' }) {
|
|
140
|
+
const [frame, setFrame] = useState(0);
|
|
141
|
+
const spinner = spinners[name];
|
|
142
|
+
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
const timer = setInterval(() => {
|
|
145
|
+
setFrame(f => (f + 1) % spinner.frames.length);
|
|
146
|
+
}, spinner.interval);
|
|
147
|
+
return () => clearInterval(timer);
|
|
148
|
+
}, [name]);
|
|
149
|
+
|
|
150
|
+
return <span>{spinner.frames[frame]} {text}</span>;
|
|
151
|
+
}
|
|
64
152
|
```
|
|
65
153
|
|
|
66
154
|
## All spinners
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unicode-animations",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Unicode spinner animations as raw frame data",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
"require": { "types": "./dist/braille.d.cts", "default": "./dist/braille.cjs" }
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"unicode-animations": "./scripts/demo.cjs"
|
|
18
|
+
},
|
|
16
19
|
"main": "./dist/index.cjs",
|
|
17
20
|
"module": "./dist/index.js",
|
|
18
21
|
"types": "./dist/index.d.ts",
|
|
@@ -20,7 +23,7 @@
|
|
|
20
23
|
"sideEffects": false,
|
|
21
24
|
"scripts": {
|
|
22
25
|
"build": "tsup",
|
|
23
|
-
"postinstall": "node scripts/postinstall.
|
|
26
|
+
"postinstall": "node scripts/postinstall.cjs",
|
|
24
27
|
"prepublishOnly": "npm run build"
|
|
25
28
|
},
|
|
26
29
|
"repository": {
|
package/scripts/demo.cjs
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
let S;
|
|
6
|
+
try {
|
|
7
|
+
S = require(path.join(__dirname, '..', 'dist', 'index.cjs'));
|
|
8
|
+
S = S.spinners || S.default;
|
|
9
|
+
} catch {
|
|
10
|
+
console.error('Run `npm run build` first.');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const names = Object.keys(S);
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
|
|
17
|
+
// Usage: npx unicode-animations [name]
|
|
18
|
+
// No args = cycle through all spinners
|
|
19
|
+
// With name = show that specific spinner
|
|
20
|
+
|
|
21
|
+
const hide = '\x1B[?25l';
|
|
22
|
+
const show = '\x1B[?25h';
|
|
23
|
+
const clear = '\x1B[2K\r';
|
|
24
|
+
const bold = '\x1B[1m';
|
|
25
|
+
const dim = '\x1B[2m';
|
|
26
|
+
const cyan = '\x1B[36m';
|
|
27
|
+
const magenta = '\x1B[35m';
|
|
28
|
+
const reset = '\x1B[0m';
|
|
29
|
+
|
|
30
|
+
process.stdout.write(hide);
|
|
31
|
+
const cleanup = () => process.stdout.write(show);
|
|
32
|
+
process.on('SIGINT', () => { cleanup(); console.log(); process.exit(0); });
|
|
33
|
+
process.on('exit', cleanup);
|
|
34
|
+
|
|
35
|
+
if (args[0] === '--list' || args[0] === '-l') {
|
|
36
|
+
cleanup();
|
|
37
|
+
console.log(`\n${bold}22 spinners available:${reset}\n`);
|
|
38
|
+
for (const name of names) {
|
|
39
|
+
const s = S[name];
|
|
40
|
+
console.log(` ${magenta}${s.frames[0]}${reset} ${name} ${dim}(${s.frames.length} frames, ${s.interval}ms)${reset}`);
|
|
41
|
+
}
|
|
42
|
+
console.log();
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (args[0] && !names.includes(args[0])) {
|
|
47
|
+
cleanup();
|
|
48
|
+
console.error(`Unknown spinner: "${args[0]}"\nRun with --list to see all spinners.`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let current = args[0] ? names.indexOf(args[0]) : 0;
|
|
53
|
+
const single = !!args[0];
|
|
54
|
+
let i = 0;
|
|
55
|
+
let ticksOnCurrent = 0;
|
|
56
|
+
|
|
57
|
+
const TICKS_PER_SPINNER = 40; // ~3.2s per spinner when cycling
|
|
58
|
+
|
|
59
|
+
const timer = setInterval(() => {
|
|
60
|
+
const name = names[current];
|
|
61
|
+
const s = S[name];
|
|
62
|
+
const frame = s.frames[i % s.frames.length];
|
|
63
|
+
const count = `${dim}[${current + 1}/${names.length}]${reset}`;
|
|
64
|
+
|
|
65
|
+
process.stdout.write(`${clear} ${magenta}${frame}${reset} ${bold}${name}${reset} ${dim}${s.interval}ms${reset} ${single ? '' : count}`);
|
|
66
|
+
|
|
67
|
+
i++;
|
|
68
|
+
ticksOnCurrent++;
|
|
69
|
+
|
|
70
|
+
if (!single && ticksOnCurrent >= TICKS_PER_SPINNER) {
|
|
71
|
+
ticksOnCurrent = 0;
|
|
72
|
+
i = 0;
|
|
73
|
+
current = (current + 1) % names.length;
|
|
74
|
+
}
|
|
75
|
+
}, 80);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const tty = require('tty');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const ci = process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.GITHUB_ACTIONS;
|
|
8
|
+
if (ci) process.exit(0);
|
|
9
|
+
|
|
10
|
+
let out;
|
|
11
|
+
try {
|
|
12
|
+
const fd = fs.openSync('/dev/tty', 'w');
|
|
13
|
+
out = new tty.WriteStream(fd);
|
|
14
|
+
} catch {
|
|
15
|
+
process.exit(0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let S;
|
|
19
|
+
try {
|
|
20
|
+
const mod = require(path.join(__dirname, '..', 'dist', 'index.cjs'));
|
|
21
|
+
S = mod.spinners || mod.default;
|
|
22
|
+
if (!S || !S.braille) throw new Error();
|
|
23
|
+
} catch {
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const DURATION = 3000;
|
|
29
|
+
const INTERVAL = 80;
|
|
30
|
+
|
|
31
|
+
const d = '\x1B[2m';
|
|
32
|
+
const b = '\x1B[1m';
|
|
33
|
+
const r = '\x1B[0m';
|
|
34
|
+
const c = '\x1B[36m';
|
|
35
|
+
const g = '\x1B[32m';
|
|
36
|
+
const w = '\x1B[37m';
|
|
37
|
+
const m = '\x1B[35m';
|
|
38
|
+
const hide = '\x1B[?25l';
|
|
39
|
+
const show = '\x1B[?25h';
|
|
40
|
+
|
|
41
|
+
out.write(hide);
|
|
42
|
+
const cleanup = () => { try { out.write(show); } catch {} };
|
|
43
|
+
process.on('SIGINT', () => { cleanup(); process.exit(0); });
|
|
44
|
+
|
|
45
|
+
// Header
|
|
46
|
+
out.write(`
|
|
47
|
+
${b}${w} ██╗ ██╗███╗ ██╗██╗ ██████╗ ██████╗ ██████╗ ███████╗${r}
|
|
48
|
+
${b}${w} ██║ ██║████╗ ██║██║██╔════╝██╔═══██╗██╔══██╗██╔════╝${r}
|
|
49
|
+
${b}${w} ██║ ██║██╔██╗ ██║██║██║ ██║ ██║██║ ██║█████╗${r}
|
|
50
|
+
${b}${w} ██║ ██║██║╚██╗██║██║██║ ██║ ██║██║ ██║██╔══╝${r}
|
|
51
|
+
${b}${w} ╚██████╔╝██║ ╚████║██║╚██████╗╚██████╔╝██████╔╝███████╗${r}
|
|
52
|
+
${d} ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝${r}
|
|
53
|
+
${b}${c} a n i m a t i o n s${r}
|
|
54
|
+
|
|
55
|
+
`);
|
|
56
|
+
|
|
57
|
+
// Braille spinners only, 2 columns of 9
|
|
58
|
+
const left = [
|
|
59
|
+
['Braille', S.braille],
|
|
60
|
+
['Orbit', S.orbit],
|
|
61
|
+
['Breathe', S.breathe],
|
|
62
|
+
['Snake', S.snake],
|
|
63
|
+
['Fill Sweep', S.fillsweep],
|
|
64
|
+
['Diag Swipe', S.diagswipe],
|
|
65
|
+
['Pulse', S.pulse],
|
|
66
|
+
['Scanline', S.scanline],
|
|
67
|
+
['Columns', S.columns],
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const right = [
|
|
71
|
+
['Checkerboard', S.checkerboard],
|
|
72
|
+
['Scan', S.scan],
|
|
73
|
+
['Rain', S.rain],
|
|
74
|
+
['Sparkle', S.sparkle],
|
|
75
|
+
['Cascade', S.cascade],
|
|
76
|
+
['Wave Rows', S.waverows],
|
|
77
|
+
['Helix', S.helix],
|
|
78
|
+
['Braille Wave', S.braillewave],
|
|
79
|
+
['DNA', S.dna],
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
const ROWS = left.length;
|
|
83
|
+
|
|
84
|
+
function pad(str, n) { return str + ' '.repeat(Math.max(0, n - str.length)); }
|
|
85
|
+
|
|
86
|
+
function renderGrid(tick) {
|
|
87
|
+
let buf = '';
|
|
88
|
+
for (let i = 0; i < ROWS; i++) {
|
|
89
|
+
const [ln, ls] = left[i];
|
|
90
|
+
const [rn, rs] = right[i];
|
|
91
|
+
const lf = ls.frames[tick % ls.frames.length];
|
|
92
|
+
const rf = rs.frames[tick % rs.frames.length];
|
|
93
|
+
buf += ` ${m}${pad(lf, 3)}${r} ${d}${pad(ln, 12)}${r} ${m}${pad(rf, 12)}${r} ${d}${rn}${r}\n`;
|
|
94
|
+
}
|
|
95
|
+
return buf;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
out.write(renderGrid(0));
|
|
99
|
+
|
|
100
|
+
let tick = 1;
|
|
101
|
+
const start = Date.now();
|
|
102
|
+
|
|
103
|
+
const timer = setInterval(() => {
|
|
104
|
+
if (Date.now() - start >= DURATION) {
|
|
105
|
+
clearInterval(timer);
|
|
106
|
+
out.write(`\n ${g}${b}✔${r} ${b}unicode-animations${r} ${d}— 18 braille spinners + 4 classics${r}\n\n`);
|
|
107
|
+
cleanup();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
out.write(`\x1B[${ROWS}A`);
|
|
111
|
+
out.write(renderGrid(tick));
|
|
112
|
+
tick++;
|
|
113
|
+
}, INTERVAL);
|
|
114
|
+
} catch {
|
|
115
|
+
try { out.write('\x1B[?25h'); } catch {}
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}
|
package/scripts/postinstall.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// Postinstall animation — showcases a few spinners side-by-side for ~1.5s
|
|
4
|
-
// Skips gracefully in CI or non-interactive environments
|
|
5
|
-
|
|
6
|
-
// npm 7+ captures ALL lifecycle script stdio (stdout + stderr) and swallows
|
|
7
|
-
// output on success. The only way to reach the real terminal is /dev/tty.
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const tty = require('tty');
|
|
10
|
-
|
|
11
|
-
const ci = process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.GITHUB_ACTIONS;
|
|
12
|
-
if (ci) process.exit(0);
|
|
13
|
-
|
|
14
|
-
let out;
|
|
15
|
-
try {
|
|
16
|
-
const fd = fs.openSync('/dev/tty', 'w');
|
|
17
|
-
out = new tty.WriteStream(fd);
|
|
18
|
-
} catch {
|
|
19
|
-
// No controlling terminal (CI, Docker, piped, etc.)
|
|
20
|
-
process.exit(0);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
const DURATION = 1500;
|
|
25
|
-
const INTERVAL = 80;
|
|
26
|
-
|
|
27
|
-
// Inline a few single-char spinners to avoid importing dist (may not exist yet)
|
|
28
|
-
const spinners = [
|
|
29
|
-
{ frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] }, // braille
|
|
30
|
-
{ frames: ['◜', '◠', '◝', '◞', '◡', '◟'] }, // arc
|
|
31
|
-
{ frames: ['◐', '◓', '◑', '◒'] }, // halfmoon
|
|
32
|
-
{ frames: ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█', '▇', '▆', '▅', '▄', '▃', '▂'] }, // blocks
|
|
33
|
-
{ frames: ['|', '/', '—', '\\'] }, // line
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const hide = '\x1B[?25l';
|
|
37
|
-
const show = '\x1B[?25h';
|
|
38
|
-
const clearLine = '\x1B[2K\r';
|
|
39
|
-
const bold = '\x1B[1m';
|
|
40
|
-
const green = '\x1B[32m';
|
|
41
|
-
const reset = '\x1B[0m';
|
|
42
|
-
|
|
43
|
-
out.write(hide);
|
|
44
|
-
|
|
45
|
-
const cleanup = () => { try { out.write(show); } catch {} };
|
|
46
|
-
process.on('SIGINT', () => { cleanup(); process.exit(0); });
|
|
47
|
-
|
|
48
|
-
let tick = 0;
|
|
49
|
-
const start = Date.now();
|
|
50
|
-
|
|
51
|
-
const timer = setInterval(() => {
|
|
52
|
-
if (Date.now() - start >= DURATION) {
|
|
53
|
-
clearInterval(timer);
|
|
54
|
-
const done = `${clearLine} ${green}${bold}✔${reset} ${bold}unicode-animations${reset} — 22 spinners ready\n`;
|
|
55
|
-
out.write(done);
|
|
56
|
-
cleanup();
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const chars = spinners.map(s => s.frames[tick % s.frames.length]);
|
|
61
|
-
out.write(`${clearLine} ${chars.join(' ')}`);
|
|
62
|
-
tick++;
|
|
63
|
-
}, INTERVAL);
|
|
64
|
-
} catch {
|
|
65
|
-
// Never block install
|
|
66
|
-
process.exit(0);
|
|
67
|
-
}
|