unicode-animations 0.1.4 → 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 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
- ## Terminal example
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 timer = setInterval(() => {
42
- process.stdout.write(`\r${frames[i++ % frames.length]} Loading...`);
43
+ const spinner = setInterval(() => {
44
+ process.stdout.write(`\r ${frames[i++ % frames.length]} Installing dependencies...`);
43
45
  }, interval);
44
46
 
45
- // Stop after 3s
46
- setTimeout(() => {
47
- clearInterval(timer);
48
- process.stdout.write('\r✔ Done! \n');
49
- }, 3000);
47
+ await install();
48
+
49
+ clearInterval(spinner);
50
+ process.stdout.write('\r Installed successfully.\n');
50
51
  ```
51
52
 
52
- ## Browser example
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 el = document.getElementById('spinner');
58
- const { frames, interval } = spinners.arc;
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
- el.textContent = frames[i++ % frames.length];
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.4",
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",
@@ -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);
@@ -1,12 +1,8 @@
1
1
  #!/usr/bin/env node
2
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
3
  const fs = require('fs');
9
4
  const tty = require('tty');
5
+ const path = require('path');
10
6
 
11
7
  const ci = process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.GITHUB_ACTIONS;
12
8
  if (ci) process.exit(0);
@@ -16,52 +12,106 @@ try {
16
12
  const fd = fs.openSync('/dev/tty', 'w');
17
13
  out = new tty.WriteStream(fd);
18
14
  } catch {
19
- // No controlling terminal (CI, Docker, piped, etc.)
20
15
  process.exit(0);
21
16
  }
22
17
 
18
+ let S;
23
19
  try {
24
- const DURATION = 1500;
25
- const INTERVAL = 80;
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
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
- ];
27
+ try {
28
+ const DURATION = 3000;
29
+ const INTERVAL = 80;
35
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';
36
38
  const hide = '\x1B[?25l';
37
39
  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
40
 
43
41
  out.write(hide);
44
-
45
42
  const cleanup = () => { try { out.write(show); } catch {} };
46
43
  process.on('SIGINT', () => { cleanup(); process.exit(0); });
47
44
 
48
- let tick = 0;
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;
49
101
  const start = Date.now();
50
102
 
51
103
  const timer = setInterval(() => {
52
104
  if (Date.now() - start >= DURATION) {
53
105
  clearInterval(timer);
54
- const done = `${clearLine} ${green}${bold}✔${reset} ${bold}unicode-animations${reset} — 22 spinners ready\n`;
55
- out.write(done);
106
+ out.write(`\n ${g}${b}✔${r} ${b}unicode-animations${r} ${d}18 braille spinners + 4 classics${r}\n\n`);
56
107
  cleanup();
57
108
  return;
58
109
  }
59
-
60
- const chars = spinners.map(s => s.frames[tick % s.frames.length]);
61
- out.write(`${clearLine} ${chars.join(' ')}`);
110
+ out.write(`\x1B[${ROWS}A`);
111
+ out.write(renderGrid(tick));
62
112
  tick++;
63
113
  }, INTERVAL);
64
114
  } catch {
65
- // Never block install
115
+ try { out.write('\x1B[?25h'); } catch {}
66
116
  process.exit(0);
67
117
  }