@cleocode/animations 2026.5.29 → 2026.5.34
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/CHANGELOG.md +77 -0
- package/README.md +290 -0
- package/dist/src/animate-context.d.ts +115 -0
- package/dist/src/animate-context.d.ts.map +1 -0
- package/dist/src/animate-context.js +85 -0
- package/dist/src/animate-context.js.map +1 -0
- package/dist/src/braille.d.ts +32 -0
- package/dist/src/braille.d.ts.map +1 -1
- package/dist/src/braille.js +52 -0
- package/dist/src/braille.js.map +1 -1
- package/dist/src/index.d.ts +18 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +23 -6
- package/dist/src/index.js.map +1 -1
- package/dist/src/progress.d.ts +50 -0
- package/dist/src/progress.d.ts.map +1 -0
- package/dist/src/progress.js +121 -0
- package/dist/src/progress.js.map +1 -0
- package/dist/src/spark.d.ts +47 -0
- package/dist/src/spark.d.ts.map +1 -0
- package/dist/src/spark.js +115 -0
- package/dist/src/spark.js.map +1 -0
- package/dist/src/spinner-handle.d.ts +97 -0
- package/dist/src/spinner-handle.d.ts.map +1 -0
- package/dist/src/spinner-handle.js +177 -0
- package/dist/src/spinner-handle.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +20 -2
- package/scripts/demo.cjs +208 -51
- package/scripts/demo.html +807 -0
package/scripts/demo.cjs
CHANGED
|
@@ -2,13 +2,19 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @cleocode/animations demo CLI.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Previews every primitive family in the terminal — generic spinners, canon
|
|
6
|
+
* spinners, progress bars, and one-shot sparks. CommonJS so the shebang works
|
|
7
|
+
* on every Node runtime without flags.
|
|
7
8
|
*
|
|
8
9
|
* Usage:
|
|
9
|
-
* cleocode-animations
|
|
10
|
-
* cleocode-animations <name>
|
|
11
|
-
* cleocode-animations
|
|
10
|
+
* cleocode-animations cycle through every primitive family
|
|
11
|
+
* cleocode-animations <name> preview one spinner (generic OR canon)
|
|
12
|
+
* cleocode-animations spark <name> play one spark and exit
|
|
13
|
+
* cleocode-animations progress loop through all 3 progress styles
|
|
14
|
+
* cleocode-animations --list list every registered primitive
|
|
15
|
+
* cleocode-animations --list-canon list canon spinner aliases only
|
|
16
|
+
* cleocode-animations --list-sparks list sparks only
|
|
17
|
+
* cleocode-animations --list-progress list progress styles only
|
|
12
18
|
*
|
|
13
19
|
* Forked from gunnargray-dev/unicode-animations (MIT).
|
|
14
20
|
*/
|
|
@@ -17,57 +23,144 @@ const path = require('path');
|
|
|
17
23
|
const fs = require('fs');
|
|
18
24
|
const tty = require('tty');
|
|
19
25
|
|
|
20
|
-
|
|
26
|
+
const MODULES = {
|
|
27
|
+
braille: 'braille.js',
|
|
28
|
+
spark: 'spark.js',
|
|
29
|
+
progress: 'progress.js',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function distPath(file) {
|
|
33
|
+
return path.join(__dirname, '..', 'dist', 'src', file);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let pending;
|
|
21
37
|
try {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
console.error('@cleocode/animations: run `pnpm --filter @cleocode/animations build` first.');
|
|
28
|
-
process.exit(1);
|
|
38
|
+
for (const file of Object.values(MODULES)) {
|
|
39
|
+
if (!fs.existsSync(distPath(file))) {
|
|
40
|
+
console.error('@cleocode/animations: run `pnpm --filter @cleocode/animations build` first.');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
29
43
|
}
|
|
30
|
-
|
|
31
|
-
|
|
44
|
+
pending = Promise.all([
|
|
45
|
+
import(distPath(MODULES.braille)),
|
|
46
|
+
import(distPath(MODULES.spark)),
|
|
47
|
+
import(distPath(MODULES.progress)),
|
|
48
|
+
]);
|
|
32
49
|
} catch (err) {
|
|
33
50
|
console.error('@cleocode/animations: failed to load the built module.', err);
|
|
34
51
|
process.exit(1);
|
|
35
52
|
}
|
|
36
53
|
|
|
37
54
|
(async () => {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
const
|
|
55
|
+
const [brailleMod, sparkMod, progressMod] = await pending;
|
|
56
|
+
|
|
57
|
+
const SPINNERS = brailleMod.spinners;
|
|
58
|
+
const CANON = brailleMod.canonSpinners;
|
|
59
|
+
const CANON_TO_GENERIC = brailleMod.CANON_TO_GENERIC;
|
|
60
|
+
const SPARKS = sparkMod.sparks;
|
|
61
|
+
const PROGRESS_STYLES = ['tapestry', 'cascade', 'refinery'];
|
|
62
|
+
const renderProgressBar = progressMod.renderProgressBar;
|
|
63
|
+
|
|
64
|
+
const spinnerNames = Object.keys(SPINNERS);
|
|
65
|
+
const canonNames = Object.keys(CANON);
|
|
66
|
+
const sparkNames = Object.keys(SPARKS);
|
|
41
67
|
const args = process.argv.slice(2);
|
|
42
68
|
|
|
69
|
+
// Color codes used by both list output (always to stdout) and the live
|
|
70
|
+
// animation surface (TTY-only). When the destination is not a TTY (e.g.
|
|
71
|
+
// piped to grep), `process.stdout.write` strips ANSI cleanly.
|
|
72
|
+
const bold = '\x1B[1m';
|
|
73
|
+
const dim = '\x1B[2m';
|
|
74
|
+
const magenta = '\x1B[35m';
|
|
75
|
+
const cyan = '\x1B[36m';
|
|
76
|
+
const yellow = '\x1B[33m';
|
|
77
|
+
const green = '\x1B[32m';
|
|
78
|
+
const reset = '\x1B[0m';
|
|
79
|
+
|
|
80
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
81
|
+
// --list family — pipe-safe, writes to stdout regardless of TTY state.
|
|
82
|
+
// Handled BEFORE the TTY-only animation path so `cleocode-animations
|
|
83
|
+
// --list | grep braille` works correctly.
|
|
84
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
85
|
+
if (args[0] === '--list' || args[0] === '-l') {
|
|
86
|
+
process.stdout.write(`\n${bold}@cleocode/animations${reset} ${dim}— primitives:${reset}\n\n`);
|
|
87
|
+
process.stdout.write(` ${bold}Spinners (generic) · ${spinnerNames.length}${reset}\n`);
|
|
88
|
+
for (const name of spinnerNames) {
|
|
89
|
+
const s = SPINNERS[name];
|
|
90
|
+
process.stdout.write(` ${magenta}${s.frames[0]}${reset} ${name} ${dim}(${s.frames.length}f, ${s.interval}ms)${reset}\n`);
|
|
91
|
+
}
|
|
92
|
+
process.stdout.write(`\n ${bold}Spinners (canon) · ${canonNames.length}${reset}\n`);
|
|
93
|
+
for (const name of canonNames) {
|
|
94
|
+
const s = CANON[name];
|
|
95
|
+
const generic = CANON_TO_GENERIC[name];
|
|
96
|
+
process.stdout.write(` ${yellow}${s.frames[0]}${reset} ${name} ${dim}→ ${generic}${reset}\n`);
|
|
97
|
+
}
|
|
98
|
+
process.stdout.write(`\n ${bold}Sparks (one-shot) · ${sparkNames.length}${reset}\n`);
|
|
99
|
+
for (const name of sparkNames) {
|
|
100
|
+
const s = SPARKS[name];
|
|
101
|
+
process.stdout.write(` ${green}${s.frames[Math.floor(s.frames.length / 2)]}${reset} ${name} ${dim}(${s.frames.length}f × ${s.interval}ms)${reset}\n`);
|
|
102
|
+
}
|
|
103
|
+
process.stdout.write(`\n ${bold}Progress styles · ${PROGRESS_STYLES.length}${reset}\n`);
|
|
104
|
+
for (const style of PROGRESS_STYLES) {
|
|
105
|
+
process.stdout.write(` ${cyan}${renderProgressBar(style, 0.5, 8)}${reset} ${style}\n`);
|
|
106
|
+
}
|
|
107
|
+
process.stdout.write('\n');
|
|
108
|
+
process.exit(0);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (args[0] === '--list-canon') {
|
|
112
|
+
for (const name of canonNames) process.stdout.write(`${name}\n`);
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}
|
|
115
|
+
if (args[0] === '--list-sparks') {
|
|
116
|
+
for (const name of sparkNames) process.stdout.write(`${name}\n`);
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
if (args[0] === '--list-progress') {
|
|
120
|
+
for (const name of PROGRESS_STYLES) process.stdout.write(`${name}\n`);
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
125
|
+
// Below this line: animation paths that require a writable TTY for the
|
|
126
|
+
// live frame replacement. If stdout is piped/redirected, fall back to
|
|
127
|
+
// /dev/tty when available, otherwise print a one-line summary and exit
|
|
128
|
+
// (matching the upstream's behavior for non-TTY usage).
|
|
129
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
43
130
|
let out = process.stdout;
|
|
44
131
|
if (!out.isTTY) {
|
|
45
132
|
try {
|
|
46
133
|
const fd = fs.openSync('/dev/tty', 'w');
|
|
47
134
|
out = new tty.WriteStream(fd);
|
|
48
135
|
} catch {
|
|
49
|
-
console.log(
|
|
136
|
+
console.log(
|
|
137
|
+
`spinners: ${spinnerNames.length} · canon: ${canonNames.length} · sparks: ${sparkNames.length} · progress: ${PROGRESS_STYLES.length}`,
|
|
138
|
+
);
|
|
139
|
+
console.log('(no TTY — pipe to a terminal or run --list to see the registry)');
|
|
50
140
|
process.exit(0);
|
|
51
141
|
}
|
|
52
142
|
}
|
|
53
143
|
|
|
54
144
|
const hide = '\x1B[?25l';
|
|
55
145
|
const show = '\x1B[?25h';
|
|
56
|
-
const bold = '\x1B[1m';
|
|
57
|
-
const dim = '\x1B[2m';
|
|
58
|
-
const magenta = '\x1B[35m';
|
|
59
|
-
const reset = '\x1B[0m';
|
|
60
|
-
|
|
61
146
|
out.write(hide);
|
|
62
|
-
const cleanup = () => {
|
|
63
|
-
|
|
147
|
+
const cleanup = () => {
|
|
148
|
+
try {
|
|
149
|
+
out.write(show);
|
|
150
|
+
} catch {}
|
|
151
|
+
};
|
|
152
|
+
process.on('SIGINT', () => {
|
|
153
|
+
cleanup();
|
|
154
|
+
out.write('\n');
|
|
155
|
+
process.exit(0);
|
|
156
|
+
});
|
|
64
157
|
process.on('exit', cleanup);
|
|
65
158
|
|
|
66
159
|
if (process.stdin.isTTY) {
|
|
67
160
|
process.stdin.setRawMode(true);
|
|
68
161
|
process.stdin.resume();
|
|
69
162
|
process.stdin.on('data', (key) => {
|
|
70
|
-
if (key[0] === 0x71 || key[0] === 0x03 || key[0] ===
|
|
163
|
+
if (key[0] === 0x71 || key[0] === 0x03 || key[0] === 0x1b) {
|
|
71
164
|
cleanup();
|
|
72
165
|
out.write('\n');
|
|
73
166
|
process.exit(0);
|
|
@@ -75,45 +168,109 @@ try {
|
|
|
75
168
|
});
|
|
76
169
|
}
|
|
77
170
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
171
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
172
|
+
// `cleocode-animations spark <name>` — play one spark and exit
|
|
173
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
174
|
+
if (args[0] === 'spark') {
|
|
175
|
+
const name = args[1];
|
|
176
|
+
if (!name || !SPARKS[name]) {
|
|
177
|
+
cleanup();
|
|
178
|
+
out.write(`Usage: cleocode-animations spark <name>\nAvailable: ${sparkNames.join(', ')}\n`);
|
|
179
|
+
process.exit(name ? 1 : 0);
|
|
180
|
+
}
|
|
181
|
+
const s = SPARKS[name];
|
|
182
|
+
for (const frame of s.frames) {
|
|
183
|
+
out.write(`\r\x1B[2K ${green}${frame}${reset} ${bold}${name}${reset}`);
|
|
184
|
+
await new Promise((r) => setTimeout(r, s.interval));
|
|
84
185
|
}
|
|
85
186
|
out.write('\n');
|
|
187
|
+
cleanup();
|
|
86
188
|
process.exit(0);
|
|
87
189
|
}
|
|
88
190
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
191
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
192
|
+
// `cleocode-animations progress` — loop through every progress style
|
|
193
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
194
|
+
if (args[0] === 'progress') {
|
|
195
|
+
let t = 0;
|
|
196
|
+
setInterval(() => {
|
|
197
|
+
const lines = PROGRESS_STYLES.map((style) => {
|
|
198
|
+
const ratio = (Math.sin(t * 0.05) + 1) / 2;
|
|
199
|
+
const bar = renderProgressBar(style, ratio, 36);
|
|
200
|
+
return ` ${cyan}${bar}${reset} ${dim}${style}${reset} ${Math.round(ratio * 100)}%`;
|
|
201
|
+
});
|
|
202
|
+
// Move cursor up to redraw all lines in place
|
|
203
|
+
if (t > 0) out.write(`\x1B[${PROGRESS_STYLES.length}A`);
|
|
204
|
+
out.write(`${lines.join('\n')}\n`);
|
|
205
|
+
t++;
|
|
206
|
+
}, 80);
|
|
207
|
+
return;
|
|
93
208
|
}
|
|
94
209
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
210
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
211
|
+
// `cleocode-animations <name>` — preview one spinner (generic OR canon)
|
|
212
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
213
|
+
function resolveAny(name) {
|
|
214
|
+
if (SPINNERS[name]) return { spinner: SPINNERS[name], color: magenta, label: name };
|
|
215
|
+
if (CANON[name])
|
|
216
|
+
return {
|
|
217
|
+
spinner: CANON[name],
|
|
218
|
+
color: yellow,
|
|
219
|
+
label: `${name} ${dim}→ ${CANON_TO_GENERIC[name]}${reset}`,
|
|
220
|
+
};
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
99
223
|
|
|
100
|
-
|
|
224
|
+
if (args[0]) {
|
|
225
|
+
const resolved = resolveAny(args[0]);
|
|
226
|
+
if (!resolved) {
|
|
227
|
+
cleanup();
|
|
228
|
+
out.write(
|
|
229
|
+
`Unknown name: "${args[0]}"\nRun --list to see every spinner / canon alias / spark / progress style.\n`,
|
|
230
|
+
);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
let i = 0;
|
|
234
|
+
setInterval(() => {
|
|
235
|
+
const f = resolved.spinner.frames[i++ % resolved.spinner.frames.length];
|
|
236
|
+
out.write(
|
|
237
|
+
`\r\x1B[2K ${resolved.color}${f}${reset} ${bold}${resolved.label}${reset} ${dim}${resolved.spinner.interval}ms${reset}`,
|
|
238
|
+
);
|
|
239
|
+
}, resolved.spinner.interval);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
101
242
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
243
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
244
|
+
// No arg — cycle through every spinner (generic + canon, deduped on object identity)
|
|
245
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
246
|
+
const tour = [];
|
|
247
|
+
for (const name of spinnerNames) tour.push({ kind: 'generic', name, spinner: SPINNERS[name] });
|
|
248
|
+
for (const name of canonNames) tour.push({ kind: 'canon', name, spinner: CANON[name] });
|
|
107
249
|
|
|
108
|
-
|
|
250
|
+
let current = 0;
|
|
251
|
+
let i = 0;
|
|
252
|
+
let ticksOnCurrent = 0;
|
|
253
|
+
const TICKS_PER = 40;
|
|
109
254
|
|
|
255
|
+
setInterval(() => {
|
|
256
|
+
const entry = tour[current];
|
|
257
|
+
const f = entry.spinner.frames[i % entry.spinner.frames.length];
|
|
258
|
+
const tag = entry.kind === 'canon' ? `${yellow}canon${reset}` : `${magenta}generic${reset}`;
|
|
259
|
+
const count = `${dim}[${current + 1}/${tour.length}]${reset}`;
|
|
260
|
+
const label =
|
|
261
|
+
entry.kind === 'canon'
|
|
262
|
+
? `${entry.name} ${dim}→ ${CANON_TO_GENERIC[entry.name]}${reset}`
|
|
263
|
+
: entry.name;
|
|
264
|
+
const color = entry.kind === 'canon' ? yellow : magenta;
|
|
265
|
+
out.write(
|
|
266
|
+
`\r\x1B[2K ${color}${f}${reset} ${tag} ${bold}${label}${reset} ${dim}${entry.spinner.interval}ms${reset} ${count}`,
|
|
267
|
+
);
|
|
110
268
|
i++;
|
|
111
269
|
ticksOnCurrent++;
|
|
112
|
-
|
|
113
|
-
if (!single && ticksOnCurrent >= TICKS_PER_SPINNER) {
|
|
270
|
+
if (ticksOnCurrent >= TICKS_PER) {
|
|
114
271
|
ticksOnCurrent = 0;
|
|
115
272
|
i = 0;
|
|
116
|
-
current = (current + 1) %
|
|
273
|
+
current = (current + 1) % tour.length;
|
|
117
274
|
}
|
|
118
275
|
}, 80);
|
|
119
276
|
})();
|