@boba-cli/progress 0.1.0-alpha.2
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 +59 -0
- package/dist/index.cjs +368 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +141 -0
- package/dist/index.d.ts +141 -0
- package/dist/index.js +365 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# @boba-cli/progress
|
|
2
|
+
|
|
3
|
+
Animated progress bar for Boba terminal UIs. Port of Charmbracelet Bubbles progress.
|
|
4
|
+
|
|
5
|
+
<img src="../../examples/progress-demo.gif" width="950" alt="Progress component demo" />
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @boba-cli/progress
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { ProgressModel, FrameMsg } from '@boba-cli/progress'
|
|
17
|
+
import type { Cmd, Msg } from '@boba-cli/tea'
|
|
18
|
+
|
|
19
|
+
let progress = ProgressModel.withDefaultGradient({ width: 30 })
|
|
20
|
+
|
|
21
|
+
function init(): Cmd<Msg> {
|
|
22
|
+
// Start at 40%
|
|
23
|
+
const [next, cmd] = progress.setPercent(0.4)
|
|
24
|
+
progress = next
|
|
25
|
+
return cmd
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function update(msg: Msg): [unknown, Cmd<Msg>] {
|
|
29
|
+
const [next, cmd] = progress.update(msg)
|
|
30
|
+
progress = next
|
|
31
|
+
return [{ progress }, cmd]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function view(): string {
|
|
35
|
+
return progress.view()
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## API
|
|
40
|
+
|
|
41
|
+
- `ProgressModel.new(options?)` create with defaults
|
|
42
|
+
- `ProgressModel.withDefaultGradient()` convenience gradient
|
|
43
|
+
- `ProgressModel.withGradient(colorA, colorB, scale?)`
|
|
44
|
+
- `ProgressModel.withSolidFill(color)`
|
|
45
|
+
- `setPercent(percent)` set target percent (0-1) and start animation
|
|
46
|
+
- `incrPercent(delta)` adjust target percent
|
|
47
|
+
- `update(msg)` handle `FrameMsg` animation ticks
|
|
48
|
+
- `view()` render bar; `percent()` exposes animated percent
|
|
49
|
+
|
|
50
|
+
## Scripts
|
|
51
|
+
|
|
52
|
+
- `pnpm -C packages/progress build`
|
|
53
|
+
- `pnpm -C packages/progress test`
|
|
54
|
+
- `pnpm -C packages/progress lint`
|
|
55
|
+
- `pnpm -C packages/progress generate:api-report`
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var tea = require('@boba-cli/tea');
|
|
4
|
+
var chapstick = require('@boba-cli/chapstick');
|
|
5
|
+
|
|
6
|
+
// src/model.ts
|
|
7
|
+
|
|
8
|
+
// src/messages.ts
|
|
9
|
+
var FrameMsg = class {
|
|
10
|
+
constructor(id, tag, time) {
|
|
11
|
+
this.id = id;
|
|
12
|
+
this.tag = tag;
|
|
13
|
+
this.time = time;
|
|
14
|
+
}
|
|
15
|
+
_tag = "progress:frame";
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/spring.ts
|
|
19
|
+
var Spring = class _Spring {
|
|
20
|
+
frequency;
|
|
21
|
+
damping;
|
|
22
|
+
#angular;
|
|
23
|
+
#pos;
|
|
24
|
+
#vel;
|
|
25
|
+
constructor(config = {}) {
|
|
26
|
+
this.frequency = config.frequency ?? 18;
|
|
27
|
+
this.damping = config.damping ?? 1;
|
|
28
|
+
this.#angular = this.frequency;
|
|
29
|
+
this.#pos = config.position ?? 0;
|
|
30
|
+
this.#vel = config.velocity ?? 0;
|
|
31
|
+
}
|
|
32
|
+
/** Current position. */
|
|
33
|
+
position() {
|
|
34
|
+
return this.#pos;
|
|
35
|
+
}
|
|
36
|
+
/** Current velocity. */
|
|
37
|
+
velocity() {
|
|
38
|
+
return this.#vel;
|
|
39
|
+
}
|
|
40
|
+
/** Return a copy with new spring options, keeping state. */
|
|
41
|
+
withOptions(frequency, damping) {
|
|
42
|
+
return new _Spring({
|
|
43
|
+
frequency,
|
|
44
|
+
damping,
|
|
45
|
+
position: this.#pos,
|
|
46
|
+
velocity: this.#vel
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Integrate toward target over the provided timestep (ms).
|
|
51
|
+
* Returns the new position and velocity.
|
|
52
|
+
*/
|
|
53
|
+
update(target, deltaMs) {
|
|
54
|
+
const dt = Math.min(0.05, Math.max(0, deltaMs / 1e3));
|
|
55
|
+
const displacement = this.#pos - target;
|
|
56
|
+
const springForce = -this.#angular * this.#angular * displacement;
|
|
57
|
+
const dampingForce = -2 * this.damping * this.#angular * this.#vel;
|
|
58
|
+
const acceleration = springForce + dampingForce;
|
|
59
|
+
const velocity = this.#vel + acceleration * dt;
|
|
60
|
+
const position = this.#pos + velocity * dt;
|
|
61
|
+
return new _Spring({
|
|
62
|
+
frequency: this.frequency,
|
|
63
|
+
damping: this.damping,
|
|
64
|
+
position,
|
|
65
|
+
velocity
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// src/gradient.ts
|
|
71
|
+
function clamp01(value) {
|
|
72
|
+
return Math.min(1, Math.max(0, value));
|
|
73
|
+
}
|
|
74
|
+
function hexToRgb(hex) {
|
|
75
|
+
const normalized = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
76
|
+
if (normalized.length !== 6) return null;
|
|
77
|
+
const int = Number.parseInt(normalized, 16);
|
|
78
|
+
if (Number.isNaN(int)) return null;
|
|
79
|
+
return {
|
|
80
|
+
r: int >> 16 & 255,
|
|
81
|
+
g: int >> 8 & 255,
|
|
82
|
+
b: int & 255
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function rgbToHex({ r, g, b }) {
|
|
86
|
+
const toHex = (v) => v.toString(16).padStart(2, "0");
|
|
87
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
88
|
+
}
|
|
89
|
+
function interpolateColor(colorA, colorB, t) {
|
|
90
|
+
const a = hexToRgb(colorA);
|
|
91
|
+
const b = hexToRgb(colorB);
|
|
92
|
+
if (!a || !b) return colorA;
|
|
93
|
+
const tClamped = clamp01(t);
|
|
94
|
+
const mix = (start, end) => Math.round(start + (end - start) * tClamped);
|
|
95
|
+
return rgbToHex({
|
|
96
|
+
r: mix(a.r, b.r),
|
|
97
|
+
g: mix(a.g, b.g),
|
|
98
|
+
b: mix(a.b, b.b)
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/model.ts
|
|
103
|
+
var FPS = 60;
|
|
104
|
+
var FRAME_MS = Math.round(1e3 / FPS);
|
|
105
|
+
var DEFAULT_ENV = chapstick.createDefaultContext().env;
|
|
106
|
+
var DEFAULT_WIDTH = 40;
|
|
107
|
+
var DEFAULT_FULL = "\u2588";
|
|
108
|
+
var DEFAULT_EMPTY = "\u2591";
|
|
109
|
+
var DEFAULT_FULL_COLOR = "#7571F9";
|
|
110
|
+
var DEFAULT_EMPTY_COLOR = "#606060";
|
|
111
|
+
var DEFAULT_PERCENT_FORMAT = " %3.0f%%";
|
|
112
|
+
var DEFAULT_GRADIENT_START = "#5A56E0";
|
|
113
|
+
var DEFAULT_GRADIENT_END = "#EE6FF8";
|
|
114
|
+
var SETTLE_DISTANCE = 2e-3;
|
|
115
|
+
var SETTLE_VELOCITY = 0.01;
|
|
116
|
+
var lastId = 0;
|
|
117
|
+
function nextId() {
|
|
118
|
+
return ++lastId;
|
|
119
|
+
}
|
|
120
|
+
function clamp012(value) {
|
|
121
|
+
if (Number.isNaN(value)) return 0;
|
|
122
|
+
return Math.max(0, Math.min(1, value));
|
|
123
|
+
}
|
|
124
|
+
function ensureChar(input, fallback) {
|
|
125
|
+
if (!input) return fallback;
|
|
126
|
+
return input.slice(0, 1);
|
|
127
|
+
}
|
|
128
|
+
function formatPercent(value, fmt) {
|
|
129
|
+
const percentValue = clamp012(value) * 100;
|
|
130
|
+
const match = fmt.match(/%(\d+)?(?:\.(\d+))?f/);
|
|
131
|
+
if (!match) {
|
|
132
|
+
return `${percentValue.toFixed(0)}%`;
|
|
133
|
+
}
|
|
134
|
+
const precision = match[2] ? Number.parseInt(match[2], 10) : 0;
|
|
135
|
+
const replacement = percentValue.toFixed(precision);
|
|
136
|
+
return fmt.replace(/%(\d+)?(?:\.(\d+))?f/, replacement).replace(/%%/g, "%");
|
|
137
|
+
}
|
|
138
|
+
function settle(percent, target, velocity) {
|
|
139
|
+
const dist = Math.abs(percent - target);
|
|
140
|
+
return dist < SETTLE_DISTANCE && Math.abs(velocity) < SETTLE_VELOCITY;
|
|
141
|
+
}
|
|
142
|
+
function resolvedColor(color, fallback) {
|
|
143
|
+
return chapstick.resolveColor(color, DEFAULT_ENV) ?? fallback;
|
|
144
|
+
}
|
|
145
|
+
function defaultState() {
|
|
146
|
+
return {
|
|
147
|
+
percent: 0,
|
|
148
|
+
target: 0,
|
|
149
|
+
velocity: 0,
|
|
150
|
+
id: nextId(),
|
|
151
|
+
tag: 0,
|
|
152
|
+
spring: new Spring(),
|
|
153
|
+
lastFrameTime: null
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
var ProgressModel = class _ProgressModel {
|
|
157
|
+
width;
|
|
158
|
+
full;
|
|
159
|
+
empty;
|
|
160
|
+
fullColor;
|
|
161
|
+
emptyColor;
|
|
162
|
+
showPercentage;
|
|
163
|
+
percentFormat;
|
|
164
|
+
gradientStart;
|
|
165
|
+
gradientEnd;
|
|
166
|
+
scaleGradient;
|
|
167
|
+
useGradient;
|
|
168
|
+
percentageStyle;
|
|
169
|
+
#percent;
|
|
170
|
+
#target;
|
|
171
|
+
#velocity;
|
|
172
|
+
#id;
|
|
173
|
+
#tag;
|
|
174
|
+
#spring;
|
|
175
|
+
#lastFrameTime;
|
|
176
|
+
constructor(options = {}, state = {}) {
|
|
177
|
+
this.width = options.width ?? DEFAULT_WIDTH;
|
|
178
|
+
this.full = ensureChar(options.full, DEFAULT_FULL);
|
|
179
|
+
this.empty = ensureChar(options.empty, DEFAULT_EMPTY);
|
|
180
|
+
this.fullColor = resolvedColor(options.fullColor, DEFAULT_FULL_COLOR);
|
|
181
|
+
this.emptyColor = resolvedColor(options.emptyColor, DEFAULT_EMPTY_COLOR);
|
|
182
|
+
this.showPercentage = options.showPercentage ?? true;
|
|
183
|
+
this.percentFormat = options.percentFormat ?? DEFAULT_PERCENT_FORMAT;
|
|
184
|
+
this.percentageStyle = options.percentageStyle ?? new chapstick.Style();
|
|
185
|
+
const start = options.gradientStart ? chapstick.resolveColor(options.gradientStart, DEFAULT_ENV) : void 0;
|
|
186
|
+
const end = options.gradientEnd ? chapstick.resolveColor(options.gradientEnd, DEFAULT_ENV) : void 0;
|
|
187
|
+
this.gradientStart = start;
|
|
188
|
+
this.gradientEnd = end;
|
|
189
|
+
this.scaleGradient = options.scaleGradient ?? false;
|
|
190
|
+
this.useGradient = Boolean(start && end);
|
|
191
|
+
const frequency = options.springFrequency ?? state.spring?.frequency ?? 18;
|
|
192
|
+
const damping = options.springDamping ?? state.spring?.damping ?? 1;
|
|
193
|
+
const baseState = { ...defaultState(), ...state };
|
|
194
|
+
const spring = state.spring ?? new Spring({ frequency, damping });
|
|
195
|
+
this.#spring = spring.withOptions(frequency, damping);
|
|
196
|
+
this.#percent = clamp012(baseState.percent);
|
|
197
|
+
this.#target = clamp012(baseState.target);
|
|
198
|
+
this.#velocity = baseState.velocity;
|
|
199
|
+
this.#id = baseState.id;
|
|
200
|
+
this.#tag = baseState.tag;
|
|
201
|
+
this.#lastFrameTime = baseState.lastFrameTime;
|
|
202
|
+
}
|
|
203
|
+
/** Create a new progress bar with defaults. */
|
|
204
|
+
static new(options = {}) {
|
|
205
|
+
return new _ProgressModel(options);
|
|
206
|
+
}
|
|
207
|
+
/** Convenience constructor with default gradient. */
|
|
208
|
+
static withDefaultGradient(options = {}) {
|
|
209
|
+
return new _ProgressModel({
|
|
210
|
+
...options,
|
|
211
|
+
gradientStart: options.gradientStart ?? DEFAULT_GRADIENT_START,
|
|
212
|
+
gradientEnd: options.gradientEnd ?? DEFAULT_GRADIENT_END
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/** Convenience constructor with a custom gradient. */
|
|
216
|
+
static withGradient(colorA, colorB, options = {}) {
|
|
217
|
+
return new _ProgressModel({
|
|
218
|
+
...options,
|
|
219
|
+
gradientStart: colorA,
|
|
220
|
+
gradientEnd: colorB
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/** Convenience constructor with solid fill. */
|
|
224
|
+
static withSolidFill(color, options = {}) {
|
|
225
|
+
return new _ProgressModel({
|
|
226
|
+
...options,
|
|
227
|
+
fullColor: color,
|
|
228
|
+
gradientStart: void 0,
|
|
229
|
+
gradientEnd: void 0
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/** Unique ID for message routing. */
|
|
233
|
+
id() {
|
|
234
|
+
return this.#id;
|
|
235
|
+
}
|
|
236
|
+
/** Current animated percent (0-1). */
|
|
237
|
+
percent() {
|
|
238
|
+
return clamp012(this.#percent);
|
|
239
|
+
}
|
|
240
|
+
/** Target percent (0-1). */
|
|
241
|
+
targetPercent() {
|
|
242
|
+
return clamp012(this.#target);
|
|
243
|
+
}
|
|
244
|
+
/** Tea init hook (no-op). */
|
|
245
|
+
init() {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
/** Handle messages; consumes FrameMsg for animation. */
|
|
249
|
+
update(msg) {
|
|
250
|
+
if (!(msg instanceof FrameMsg)) {
|
|
251
|
+
return [this, null];
|
|
252
|
+
}
|
|
253
|
+
if (msg.id !== this.#id || msg.tag !== this.#tag) {
|
|
254
|
+
return [this, null];
|
|
255
|
+
}
|
|
256
|
+
const dt = this.#lastFrameTime === null ? FRAME_MS : Math.max(1, msg.time.getTime() - this.#lastFrameTime.getTime());
|
|
257
|
+
const spring = this.#spring.update(this.#target, dt);
|
|
258
|
+
const nextPercent = clamp012(spring.position());
|
|
259
|
+
const nextVelocity = spring.velocity();
|
|
260
|
+
const next = this.withState({
|
|
261
|
+
percent: nextPercent,
|
|
262
|
+
velocity: nextVelocity,
|
|
263
|
+
tag: this.#tag,
|
|
264
|
+
spring,
|
|
265
|
+
lastFrameTime: msg.time
|
|
266
|
+
});
|
|
267
|
+
if (settle(nextPercent, this.#target, nextVelocity)) {
|
|
268
|
+
return [next, null];
|
|
269
|
+
}
|
|
270
|
+
return [next, next.nextFrame()];
|
|
271
|
+
}
|
|
272
|
+
/** Set a new target percent and start animation. */
|
|
273
|
+
setPercent(percent) {
|
|
274
|
+
const clamped = clamp012(percent);
|
|
275
|
+
const next = this.withState({
|
|
276
|
+
target: clamped,
|
|
277
|
+
tag: this.#tag + 1,
|
|
278
|
+
lastFrameTime: null
|
|
279
|
+
});
|
|
280
|
+
return [next, next.nextFrame()];
|
|
281
|
+
}
|
|
282
|
+
/** Increment the target percent. */
|
|
283
|
+
incrPercent(delta) {
|
|
284
|
+
return this.setPercent(this.#target + delta);
|
|
285
|
+
}
|
|
286
|
+
/** Update the spring configuration (keeps current state). */
|
|
287
|
+
setSpringOptions(frequency, damping) {
|
|
288
|
+
const spring = this.#spring.withOptions(frequency, damping);
|
|
289
|
+
return this.withState({ spring });
|
|
290
|
+
}
|
|
291
|
+
/** Render the animated progress bar. */
|
|
292
|
+
view() {
|
|
293
|
+
return this.viewAs(this.percent());
|
|
294
|
+
}
|
|
295
|
+
/** Render the bar at an explicit percent (0-1). */
|
|
296
|
+
viewAs(percent) {
|
|
297
|
+
const pct = clamp012(percent);
|
|
298
|
+
const percentText = this.showPercentage ? this.percentageStyle.render(formatPercent(pct, this.percentFormat)) : "";
|
|
299
|
+
const percentWidth = this.showPercentage ? chapstick.width(percentText) : 0;
|
|
300
|
+
const totalBarWidth = Math.max(0, this.width - percentWidth);
|
|
301
|
+
const filledWidth = Math.max(0, Math.round(totalBarWidth * pct));
|
|
302
|
+
const emptyWidth = Math.max(0, totalBarWidth - filledWidth);
|
|
303
|
+
const bar = `${this.useGradient ? this.renderGradient(filledWidth, totalBarWidth) : this.renderSolid(filledWidth)}${this.renderEmpty(emptyWidth)}`;
|
|
304
|
+
return `${bar}${percentText}`;
|
|
305
|
+
}
|
|
306
|
+
renderGradient(filledWidth, totalWidth) {
|
|
307
|
+
if (!this.useGradient || filledWidth <= 0 || !this.gradientStart || !this.gradientEnd) {
|
|
308
|
+
return "";
|
|
309
|
+
}
|
|
310
|
+
const parts = [];
|
|
311
|
+
const denominator = this.scaleGradient ? Math.max(1, filledWidth - 1) : Math.max(1, totalWidth - 1);
|
|
312
|
+
for (let i = 0; i < filledWidth; i++) {
|
|
313
|
+
const t = filledWidth === 1 ? 0.5 : i / denominator;
|
|
314
|
+
const color = interpolateColor(this.gradientStart, this.gradientEnd, t);
|
|
315
|
+
parts.push(new chapstick.Style().foreground(color).render(this.full));
|
|
316
|
+
}
|
|
317
|
+
return parts.join("");
|
|
318
|
+
}
|
|
319
|
+
renderSolid(filledWidth) {
|
|
320
|
+
if (filledWidth <= 0) return "";
|
|
321
|
+
const styled = new chapstick.Style().foreground(this.fullColor).render(this.full);
|
|
322
|
+
return styled.repeat(filledWidth);
|
|
323
|
+
}
|
|
324
|
+
renderEmpty(emptyWidth) {
|
|
325
|
+
if (emptyWidth <= 0) return "";
|
|
326
|
+
const styled = new chapstick.Style().foreground(this.emptyColor).render(this.empty);
|
|
327
|
+
return styled.repeat(emptyWidth);
|
|
328
|
+
}
|
|
329
|
+
nextFrame() {
|
|
330
|
+
const id = this.#id;
|
|
331
|
+
const tag = this.#tag;
|
|
332
|
+
return tea.tick(FRAME_MS, (time) => new FrameMsg(id, tag, time));
|
|
333
|
+
}
|
|
334
|
+
withState(state) {
|
|
335
|
+
return new _ProgressModel(
|
|
336
|
+
{
|
|
337
|
+
width: this.width,
|
|
338
|
+
full: this.full,
|
|
339
|
+
empty: this.empty,
|
|
340
|
+
fullColor: this.fullColor,
|
|
341
|
+
emptyColor: this.emptyColor,
|
|
342
|
+
showPercentage: this.showPercentage,
|
|
343
|
+
percentFormat: this.percentFormat,
|
|
344
|
+
gradientStart: this.gradientStart,
|
|
345
|
+
gradientEnd: this.gradientEnd,
|
|
346
|
+
scaleGradient: this.scaleGradient,
|
|
347
|
+
springFrequency: this.#spring.frequency,
|
|
348
|
+
springDamping: this.#spring.damping,
|
|
349
|
+
percentageStyle: this.percentageStyle
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
percent: this.#percent,
|
|
353
|
+
target: this.#target,
|
|
354
|
+
velocity: this.#velocity,
|
|
355
|
+
id: this.#id,
|
|
356
|
+
tag: this.#tag,
|
|
357
|
+
spring: this.#spring,
|
|
358
|
+
lastFrameTime: this.#lastFrameTime,
|
|
359
|
+
...state
|
|
360
|
+
}
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
exports.FrameMsg = FrameMsg;
|
|
366
|
+
exports.ProgressModel = ProgressModel;
|
|
367
|
+
//# sourceMappingURL=index.cjs.map
|
|
368
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/messages.ts","../src/spring.ts","../src/gradient.ts","../src/model.ts"],"names":["createDefaultContext","clamp01","resolveColor","Style","textWidth","tick"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Message indicating a progress animation frame should be rendered.\n * @public\n */\nexport class FrameMsg {\n readonly _tag = 'progress:frame'\n\n constructor(\n /** Unique progress ID for routing */\n public readonly id: number,\n /** Internal tag to prevent duplicate ticks */\n public readonly tag: number,\n /** Timestamp when the frame was scheduled */\n public readonly time: Date,\n ) {}\n}\n","interface SpringConfig {\n /** Oscillation speed (Hz). */\n frequency?: number\n /** Damping factor (1.0 = critical-ish). */\n damping?: number\n /** Starting position (0-1). */\n position?: number\n /** Starting velocity. */\n velocity?: number\n}\n\n/**\n * Minimal damped spring integrator (ported from harmonica).\n * Stores its own position/velocity and integrates using a simple\n * damped harmonic oscillator step.\n */\nexport class Spring {\n readonly frequency: number\n readonly damping: number\n readonly #angular: number\n readonly #pos: number\n readonly #vel: number\n\n constructor(config: SpringConfig = {}) {\n this.frequency = config.frequency ?? 18\n this.damping = config.damping ?? 1\n // Note: using the provided frequency directly (not 2π) keeps the\n // explicit Euler step stable at ~60 FPS for our use case.\n this.#angular = this.frequency\n this.#pos = config.position ?? 0\n this.#vel = config.velocity ?? 0\n }\n\n /** Current position. */\n position(): number {\n return this.#pos\n }\n\n /** Current velocity. */\n velocity(): number {\n return this.#vel\n }\n\n /** Return a copy with new spring options, keeping state. */\n withOptions(frequency: number, damping: number): Spring {\n return new Spring({\n frequency,\n damping,\n position: this.#pos,\n velocity: this.#vel,\n })\n }\n\n /**\n * Integrate toward target over the provided timestep (ms).\n * Returns the new position and velocity.\n */\n update(target: number, deltaMs: number): Spring {\n // Clamp dt to avoid instability on slow frames.\n const dt = Math.min(0.05, Math.max(0, deltaMs / 1000))\n const displacement = this.#pos - target\n\n const springForce = -this.#angular * this.#angular * displacement\n const dampingForce = -2 * this.damping * this.#angular * this.#vel\n const acceleration = springForce + dampingForce\n\n const velocity = this.#vel + acceleration * dt\n const position = this.#pos + velocity * dt\n\n return new Spring({\n frequency: this.frequency,\n damping: this.damping,\n position,\n velocity,\n })\n }\n}\n","interface RGB {\n r: number\n g: number\n b: number\n}\n\nfunction clamp01(value: number): number {\n return Math.min(1, Math.max(0, value))\n}\n\nfunction hexToRgb(hex: string): RGB | null {\n const normalized = hex.startsWith('#') ? hex.slice(1) : hex\n if (normalized.length !== 6) return null\n const int = Number.parseInt(normalized, 16)\n if (Number.isNaN(int)) return null\n return {\n r: (int >> 16) & 0xff,\n g: (int >> 8) & 0xff,\n b: int & 0xff,\n }\n}\n\nfunction rgbToHex({ r, g, b }: RGB): string {\n const toHex = (v: number) => v.toString(16).padStart(2, '0')\n return `#${toHex(r)}${toHex(g)}${toHex(b)}`\n}\n\n/**\n * Linearly interpolate between two hex colors in RGB space.\n * Returns the first color if parsing fails.\n */\nexport function interpolateColor(\n colorA: string,\n colorB: string,\n t: number,\n): string {\n const a = hexToRgb(colorA)\n const b = hexToRgb(colorB)\n if (!a || !b) return colorA\n\n const tClamped = clamp01(t)\n const mix = (start: number, end: number) =>\n Math.round(start + (end - start) * tClamped)\n\n return rgbToHex({\n r: mix(a.r, b.r),\n g: mix(a.g, b.g),\n b: mix(a.b, b.b),\n })\n}\n","import { tick, type Cmd, type Msg } from '@boba-cli/tea'\nimport {\n Style,\n createDefaultContext,\n resolveColor,\n width as textWidth,\n type ColorInput,\n} from '@boba-cli/chapstick'\nimport { FrameMsg } from './messages.js'\nimport { Spring } from './spring.js'\nimport { interpolateColor } from './gradient.js'\n\nconst FPS = 60\nconst FRAME_MS = Math.round(1000 / FPS)\nconst DEFAULT_ENV = createDefaultContext().env\nconst DEFAULT_WIDTH = 40\nconst DEFAULT_FULL = '█'\nconst DEFAULT_EMPTY = '░'\nconst DEFAULT_FULL_COLOR = '#7571F9'\nconst DEFAULT_EMPTY_COLOR = '#606060'\nconst DEFAULT_PERCENT_FORMAT = ' %3.0f%%'\nconst DEFAULT_GRADIENT_START = '#5A56E0'\nconst DEFAULT_GRADIENT_END = '#EE6FF8'\nconst SETTLE_DISTANCE = 0.002\nconst SETTLE_VELOCITY = 0.01\n\n// Module-level ID counter for message routing\nlet lastId = 0\nfunction nextId(): number {\n return ++lastId\n}\n\nfunction clamp01(value: number): number {\n if (Number.isNaN(value)) return 0\n return Math.max(0, Math.min(1, value))\n}\n\nfunction ensureChar(input: string | undefined, fallback: string): string {\n if (!input) return fallback\n // Use the first Unicode grapheme; for simplicity take first code unit\n return input.slice(0, 1)\n}\n\nfunction formatPercent(value: number, fmt: string): string {\n const percentValue = clamp01(value) * 100\n const match = fmt.match(/%(\\d+)?(?:\\.(\\d+))?f/)\n if (!match) {\n return `${percentValue.toFixed(0)}%`\n }\n const precision = match[2] ? Number.parseInt(match[2], 10) : 0\n const replacement = percentValue.toFixed(precision)\n return fmt.replace(/%(\\d+)?(?:\\.(\\d+))?f/, replacement).replace(/%%/g, '%')\n}\n\nfunction settle(percent: number, target: number, velocity: number): boolean {\n const dist = Math.abs(percent - target)\n return dist < SETTLE_DISTANCE && Math.abs(velocity) < SETTLE_VELOCITY\n}\n\nfunction resolvedColor(\n color: ColorInput | undefined,\n fallback: string,\n): string {\n return resolveColor(color, DEFAULT_ENV) ?? fallback\n}\n\n/**\n * Options for the progress bar model.\n * @public\n */\nexport interface ProgressOptions {\n width?: number\n full?: string\n empty?: string\n fullColor?: ColorInput\n emptyColor?: ColorInput\n showPercentage?: boolean\n percentFormat?: string\n gradientStart?: ColorInput\n gradientEnd?: ColorInput\n scaleGradient?: boolean\n springFrequency?: number\n springDamping?: number\n percentageStyle?: Style\n}\n\ninterface ProgressState {\n percent: number\n target: number\n velocity: number\n id: number\n tag: number\n spring: Spring\n lastFrameTime: Date | null\n}\n\ntype ProgressInit = Partial<ProgressState>\n\nfunction defaultState(): ProgressState {\n return {\n percent: 0,\n target: 0,\n velocity: 0,\n id: nextId(),\n tag: 0,\n spring: new Spring(),\n lastFrameTime: null,\n }\n}\n\n/**\n * Animated progress bar model with spring-based easing.\n * @public\n */\nexport class ProgressModel {\n readonly width: number\n readonly full: string\n readonly empty: string\n readonly fullColor: string\n readonly emptyColor: string\n readonly showPercentage: boolean\n readonly percentFormat: string\n readonly gradientStart?: string\n readonly gradientEnd?: string\n readonly scaleGradient: boolean\n readonly useGradient: boolean\n readonly percentageStyle: Style\n\n readonly #percent: number\n readonly #target: number\n readonly #velocity: number\n readonly #id: number\n readonly #tag: number\n readonly #spring: Spring\n readonly #lastFrameTime: Date | null\n\n constructor(options: ProgressOptions = {}, state: ProgressInit = {}) {\n this.width = options.width ?? DEFAULT_WIDTH\n this.full = ensureChar(options.full, DEFAULT_FULL)\n this.empty = ensureChar(options.empty, DEFAULT_EMPTY)\n this.fullColor = resolvedColor(options.fullColor, DEFAULT_FULL_COLOR)\n this.emptyColor = resolvedColor(options.emptyColor, DEFAULT_EMPTY_COLOR)\n this.showPercentage = options.showPercentage ?? true\n this.percentFormat = options.percentFormat ?? DEFAULT_PERCENT_FORMAT\n this.percentageStyle = options.percentageStyle ?? new Style()\n\n const start = options.gradientStart\n ? resolveColor(options.gradientStart, DEFAULT_ENV)\n : undefined\n const end = options.gradientEnd\n ? resolveColor(options.gradientEnd, DEFAULT_ENV)\n : undefined\n this.gradientStart = start\n this.gradientEnd = end\n this.scaleGradient = options.scaleGradient ?? false\n this.useGradient = Boolean(start && end)\n\n const frequency = options.springFrequency ?? state.spring?.frequency ?? 18\n const damping = options.springDamping ?? state.spring?.damping ?? 1\n const baseState = { ...defaultState(), ...state }\n const spring = state.spring ?? new Spring({ frequency, damping })\n this.#spring = spring.withOptions(frequency, damping)\n\n this.#percent = clamp01(baseState.percent)\n this.#target = clamp01(baseState.target)\n this.#velocity = baseState.velocity\n this.#id = baseState.id\n this.#tag = baseState.tag\n this.#lastFrameTime = baseState.lastFrameTime\n }\n\n /** Create a new progress bar with defaults. */\n static new(options: ProgressOptions = {}): ProgressModel {\n return new ProgressModel(options)\n }\n\n /** Convenience constructor with default gradient. */\n static withDefaultGradient(options: ProgressOptions = {}): ProgressModel {\n return new ProgressModel({\n ...options,\n gradientStart: options.gradientStart ?? DEFAULT_GRADIENT_START,\n gradientEnd: options.gradientEnd ?? DEFAULT_GRADIENT_END,\n })\n }\n\n /** Convenience constructor with a custom gradient. */\n static withGradient(\n colorA: ColorInput,\n colorB: ColorInput,\n options: ProgressOptions = {},\n ): ProgressModel {\n return new ProgressModel({\n ...options,\n gradientStart: colorA,\n gradientEnd: colorB,\n })\n }\n\n /** Convenience constructor with solid fill. */\n static withSolidFill(\n color: ColorInput,\n options: ProgressOptions = {},\n ): ProgressModel {\n return new ProgressModel({\n ...options,\n fullColor: color,\n gradientStart: undefined,\n gradientEnd: undefined,\n })\n }\n\n /** Unique ID for message routing. */\n id(): number {\n return this.#id\n }\n\n /** Current animated percent (0-1). */\n percent(): number {\n return clamp01(this.#percent)\n }\n\n /** Target percent (0-1). */\n targetPercent(): number {\n return clamp01(this.#target)\n }\n\n /** Tea init hook (no-op). */\n init(): Cmd<Msg> {\n return null\n }\n\n /** Handle messages; consumes FrameMsg for animation. */\n update(msg: Msg): [ProgressModel, Cmd<Msg>] {\n if (!(msg instanceof FrameMsg)) {\n return [this, null]\n }\n if (msg.id !== this.#id || msg.tag !== this.#tag) {\n return [this, null]\n }\n\n const dt =\n this.#lastFrameTime === null\n ? FRAME_MS\n : Math.max(1, msg.time.getTime() - this.#lastFrameTime.getTime())\n\n const spring = this.#spring.update(this.#target, dt)\n const nextPercent = clamp01(spring.position())\n const nextVelocity = spring.velocity()\n\n const next = this.withState({\n percent: nextPercent,\n velocity: nextVelocity,\n tag: this.#tag,\n spring,\n lastFrameTime: msg.time,\n })\n\n if (settle(nextPercent, this.#target, nextVelocity)) {\n return [next, null]\n }\n\n return [next, next.nextFrame()]\n }\n\n /** Set a new target percent and start animation. */\n setPercent(percent: number): [ProgressModel, Cmd<Msg>] {\n const clamped = clamp01(percent)\n const next = this.withState({\n target: clamped,\n tag: this.#tag + 1,\n lastFrameTime: null,\n })\n return [next, next.nextFrame()]\n }\n\n /** Increment the target percent. */\n incrPercent(delta: number): [ProgressModel, Cmd<Msg>] {\n return this.setPercent(this.#target + delta)\n }\n\n /** Update the spring configuration (keeps current state). */\n setSpringOptions(frequency: number, damping: number): ProgressModel {\n const spring = this.#spring.withOptions(frequency, damping)\n return this.withState({ spring })\n }\n\n /** Render the animated progress bar. */\n view(): string {\n return this.viewAs(this.percent())\n }\n\n /** Render the bar at an explicit percent (0-1). */\n viewAs(percent: number): string {\n const pct = clamp01(percent)\n const percentText = this.showPercentage\n ? this.percentageStyle.render(formatPercent(pct, this.percentFormat))\n : ''\n\n const percentWidth = this.showPercentage ? textWidth(percentText) : 0\n const totalBarWidth = Math.max(0, this.width - percentWidth)\n const filledWidth = Math.max(0, Math.round(totalBarWidth * pct))\n const emptyWidth = Math.max(0, totalBarWidth - filledWidth)\n\n const bar = `${this.useGradient ? this.renderGradient(filledWidth, totalBarWidth) : this.renderSolid(filledWidth)}${this.renderEmpty(emptyWidth)}`\n\n return `${bar}${percentText}`\n }\n\n private renderGradient(filledWidth: number, totalWidth: number): string {\n if (\n !this.useGradient ||\n filledWidth <= 0 ||\n !this.gradientStart ||\n !this.gradientEnd\n ) {\n return ''\n }\n\n const parts: string[] = []\n const denominator = this.scaleGradient\n ? Math.max(1, filledWidth - 1)\n : Math.max(1, totalWidth - 1)\n\n for (let i = 0; i < filledWidth; i++) {\n const t = filledWidth === 1 ? 0.5 : i / denominator\n const color = interpolateColor(this.gradientStart, this.gradientEnd, t)\n parts.push(new Style().foreground(color).render(this.full))\n }\n\n return parts.join('')\n }\n\n private renderSolid(filledWidth: number): string {\n if (filledWidth <= 0) return ''\n const styled = new Style().foreground(this.fullColor).render(this.full)\n return styled.repeat(filledWidth)\n }\n\n private renderEmpty(emptyWidth: number): string {\n if (emptyWidth <= 0) return ''\n const styled = new Style().foreground(this.emptyColor).render(this.empty)\n return styled.repeat(emptyWidth)\n }\n\n private nextFrame(): Cmd<FrameMsg> {\n const id = this.#id\n const tag = this.#tag\n return tick(FRAME_MS, (time) => new FrameMsg(id, tag, time))\n }\n\n private withState(state: ProgressInit): ProgressModel {\n return new ProgressModel(\n {\n width: this.width,\n full: this.full,\n empty: this.empty,\n fullColor: this.fullColor,\n emptyColor: this.emptyColor,\n showPercentage: this.showPercentage,\n percentFormat: this.percentFormat,\n gradientStart: this.gradientStart,\n gradientEnd: this.gradientEnd,\n scaleGradient: this.scaleGradient,\n springFrequency: this.#spring.frequency,\n springDamping: this.#spring.damping,\n percentageStyle: this.percentageStyle,\n },\n {\n percent: this.#percent,\n target: this.#target,\n velocity: this.#velocity,\n id: this.#id,\n tag: this.#tag,\n spring: this.#spring,\n lastFrameTime: this.#lastFrameTime,\n ...state,\n },\n )\n }\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Cmd, Msg } from '@boba-cli/tea';
|
|
2
|
+
import { Style, ColorInput } from '@boba-cli/chapstick';
|
|
3
|
+
|
|
4
|
+
interface SpringConfig {
|
|
5
|
+
/** Oscillation speed (Hz). */
|
|
6
|
+
frequency?: number;
|
|
7
|
+
/** Damping factor (1.0 = critical-ish). */
|
|
8
|
+
damping?: number;
|
|
9
|
+
/** Starting position (0-1). */
|
|
10
|
+
position?: number;
|
|
11
|
+
/** Starting velocity. */
|
|
12
|
+
velocity?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Minimal damped spring integrator (ported from harmonica).
|
|
16
|
+
* Stores its own position/velocity and integrates using a simple
|
|
17
|
+
* damped harmonic oscillator step.
|
|
18
|
+
*/
|
|
19
|
+
declare class Spring {
|
|
20
|
+
#private;
|
|
21
|
+
readonly frequency: number;
|
|
22
|
+
readonly damping: number;
|
|
23
|
+
constructor(config?: SpringConfig);
|
|
24
|
+
/** Current position. */
|
|
25
|
+
position(): number;
|
|
26
|
+
/** Current velocity. */
|
|
27
|
+
velocity(): number;
|
|
28
|
+
/** Return a copy with new spring options, keeping state. */
|
|
29
|
+
withOptions(frequency: number, damping: number): Spring;
|
|
30
|
+
/**
|
|
31
|
+
* Integrate toward target over the provided timestep (ms).
|
|
32
|
+
* Returns the new position and velocity.
|
|
33
|
+
*/
|
|
34
|
+
update(target: number, deltaMs: number): Spring;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Options for the progress bar model.
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
interface ProgressOptions {
|
|
42
|
+
width?: number;
|
|
43
|
+
full?: string;
|
|
44
|
+
empty?: string;
|
|
45
|
+
fullColor?: ColorInput;
|
|
46
|
+
emptyColor?: ColorInput;
|
|
47
|
+
showPercentage?: boolean;
|
|
48
|
+
percentFormat?: string;
|
|
49
|
+
gradientStart?: ColorInput;
|
|
50
|
+
gradientEnd?: ColorInput;
|
|
51
|
+
scaleGradient?: boolean;
|
|
52
|
+
springFrequency?: number;
|
|
53
|
+
springDamping?: number;
|
|
54
|
+
percentageStyle?: Style;
|
|
55
|
+
}
|
|
56
|
+
interface ProgressState {
|
|
57
|
+
percent: number;
|
|
58
|
+
target: number;
|
|
59
|
+
velocity: number;
|
|
60
|
+
id: number;
|
|
61
|
+
tag: number;
|
|
62
|
+
spring: Spring;
|
|
63
|
+
lastFrameTime: Date | null;
|
|
64
|
+
}
|
|
65
|
+
type ProgressInit = Partial<ProgressState>;
|
|
66
|
+
/**
|
|
67
|
+
* Animated progress bar model with spring-based easing.
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
70
|
+
declare class ProgressModel {
|
|
71
|
+
#private;
|
|
72
|
+
readonly width: number;
|
|
73
|
+
readonly full: string;
|
|
74
|
+
readonly empty: string;
|
|
75
|
+
readonly fullColor: string;
|
|
76
|
+
readonly emptyColor: string;
|
|
77
|
+
readonly showPercentage: boolean;
|
|
78
|
+
readonly percentFormat: string;
|
|
79
|
+
readonly gradientStart?: string;
|
|
80
|
+
readonly gradientEnd?: string;
|
|
81
|
+
readonly scaleGradient: boolean;
|
|
82
|
+
readonly useGradient: boolean;
|
|
83
|
+
readonly percentageStyle: Style;
|
|
84
|
+
constructor(options?: ProgressOptions, state?: ProgressInit);
|
|
85
|
+
/** Create a new progress bar with defaults. */
|
|
86
|
+
static new(options?: ProgressOptions): ProgressModel;
|
|
87
|
+
/** Convenience constructor with default gradient. */
|
|
88
|
+
static withDefaultGradient(options?: ProgressOptions): ProgressModel;
|
|
89
|
+
/** Convenience constructor with a custom gradient. */
|
|
90
|
+
static withGradient(colorA: ColorInput, colorB: ColorInput, options?: ProgressOptions): ProgressModel;
|
|
91
|
+
/** Convenience constructor with solid fill. */
|
|
92
|
+
static withSolidFill(color: ColorInput, options?: ProgressOptions): ProgressModel;
|
|
93
|
+
/** Unique ID for message routing. */
|
|
94
|
+
id(): number;
|
|
95
|
+
/** Current animated percent (0-1). */
|
|
96
|
+
percent(): number;
|
|
97
|
+
/** Target percent (0-1). */
|
|
98
|
+
targetPercent(): number;
|
|
99
|
+
/** Tea init hook (no-op). */
|
|
100
|
+
init(): Cmd<Msg>;
|
|
101
|
+
/** Handle messages; consumes FrameMsg for animation. */
|
|
102
|
+
update(msg: Msg): [ProgressModel, Cmd<Msg>];
|
|
103
|
+
/** Set a new target percent and start animation. */
|
|
104
|
+
setPercent(percent: number): [ProgressModel, Cmd<Msg>];
|
|
105
|
+
/** Increment the target percent. */
|
|
106
|
+
incrPercent(delta: number): [ProgressModel, Cmd<Msg>];
|
|
107
|
+
/** Update the spring configuration (keeps current state). */
|
|
108
|
+
setSpringOptions(frequency: number, damping: number): ProgressModel;
|
|
109
|
+
/** Render the animated progress bar. */
|
|
110
|
+
view(): string;
|
|
111
|
+
/** Render the bar at an explicit percent (0-1). */
|
|
112
|
+
viewAs(percent: number): string;
|
|
113
|
+
private renderGradient;
|
|
114
|
+
private renderSolid;
|
|
115
|
+
private renderEmpty;
|
|
116
|
+
private nextFrame;
|
|
117
|
+
private withState;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Message indicating a progress animation frame should be rendered.
|
|
122
|
+
* @public
|
|
123
|
+
*/
|
|
124
|
+
declare class FrameMsg {
|
|
125
|
+
/** Unique progress ID for routing */
|
|
126
|
+
readonly id: number;
|
|
127
|
+
/** Internal tag to prevent duplicate ticks */
|
|
128
|
+
readonly tag: number;
|
|
129
|
+
/** Timestamp when the frame was scheduled */
|
|
130
|
+
readonly time: Date;
|
|
131
|
+
readonly _tag = "progress:frame";
|
|
132
|
+
constructor(
|
|
133
|
+
/** Unique progress ID for routing */
|
|
134
|
+
id: number,
|
|
135
|
+
/** Internal tag to prevent duplicate ticks */
|
|
136
|
+
tag: number,
|
|
137
|
+
/** Timestamp when the frame was scheduled */
|
|
138
|
+
time: Date);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { FrameMsg, ProgressModel, type ProgressOptions };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Cmd, Msg } from '@boba-cli/tea';
|
|
2
|
+
import { Style, ColorInput } from '@boba-cli/chapstick';
|
|
3
|
+
|
|
4
|
+
interface SpringConfig {
|
|
5
|
+
/** Oscillation speed (Hz). */
|
|
6
|
+
frequency?: number;
|
|
7
|
+
/** Damping factor (1.0 = critical-ish). */
|
|
8
|
+
damping?: number;
|
|
9
|
+
/** Starting position (0-1). */
|
|
10
|
+
position?: number;
|
|
11
|
+
/** Starting velocity. */
|
|
12
|
+
velocity?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Minimal damped spring integrator (ported from harmonica).
|
|
16
|
+
* Stores its own position/velocity and integrates using a simple
|
|
17
|
+
* damped harmonic oscillator step.
|
|
18
|
+
*/
|
|
19
|
+
declare class Spring {
|
|
20
|
+
#private;
|
|
21
|
+
readonly frequency: number;
|
|
22
|
+
readonly damping: number;
|
|
23
|
+
constructor(config?: SpringConfig);
|
|
24
|
+
/** Current position. */
|
|
25
|
+
position(): number;
|
|
26
|
+
/** Current velocity. */
|
|
27
|
+
velocity(): number;
|
|
28
|
+
/** Return a copy with new spring options, keeping state. */
|
|
29
|
+
withOptions(frequency: number, damping: number): Spring;
|
|
30
|
+
/**
|
|
31
|
+
* Integrate toward target over the provided timestep (ms).
|
|
32
|
+
* Returns the new position and velocity.
|
|
33
|
+
*/
|
|
34
|
+
update(target: number, deltaMs: number): Spring;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Options for the progress bar model.
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
interface ProgressOptions {
|
|
42
|
+
width?: number;
|
|
43
|
+
full?: string;
|
|
44
|
+
empty?: string;
|
|
45
|
+
fullColor?: ColorInput;
|
|
46
|
+
emptyColor?: ColorInput;
|
|
47
|
+
showPercentage?: boolean;
|
|
48
|
+
percentFormat?: string;
|
|
49
|
+
gradientStart?: ColorInput;
|
|
50
|
+
gradientEnd?: ColorInput;
|
|
51
|
+
scaleGradient?: boolean;
|
|
52
|
+
springFrequency?: number;
|
|
53
|
+
springDamping?: number;
|
|
54
|
+
percentageStyle?: Style;
|
|
55
|
+
}
|
|
56
|
+
interface ProgressState {
|
|
57
|
+
percent: number;
|
|
58
|
+
target: number;
|
|
59
|
+
velocity: number;
|
|
60
|
+
id: number;
|
|
61
|
+
tag: number;
|
|
62
|
+
spring: Spring;
|
|
63
|
+
lastFrameTime: Date | null;
|
|
64
|
+
}
|
|
65
|
+
type ProgressInit = Partial<ProgressState>;
|
|
66
|
+
/**
|
|
67
|
+
* Animated progress bar model with spring-based easing.
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
70
|
+
declare class ProgressModel {
|
|
71
|
+
#private;
|
|
72
|
+
readonly width: number;
|
|
73
|
+
readonly full: string;
|
|
74
|
+
readonly empty: string;
|
|
75
|
+
readonly fullColor: string;
|
|
76
|
+
readonly emptyColor: string;
|
|
77
|
+
readonly showPercentage: boolean;
|
|
78
|
+
readonly percentFormat: string;
|
|
79
|
+
readonly gradientStart?: string;
|
|
80
|
+
readonly gradientEnd?: string;
|
|
81
|
+
readonly scaleGradient: boolean;
|
|
82
|
+
readonly useGradient: boolean;
|
|
83
|
+
readonly percentageStyle: Style;
|
|
84
|
+
constructor(options?: ProgressOptions, state?: ProgressInit);
|
|
85
|
+
/** Create a new progress bar with defaults. */
|
|
86
|
+
static new(options?: ProgressOptions): ProgressModel;
|
|
87
|
+
/** Convenience constructor with default gradient. */
|
|
88
|
+
static withDefaultGradient(options?: ProgressOptions): ProgressModel;
|
|
89
|
+
/** Convenience constructor with a custom gradient. */
|
|
90
|
+
static withGradient(colorA: ColorInput, colorB: ColorInput, options?: ProgressOptions): ProgressModel;
|
|
91
|
+
/** Convenience constructor with solid fill. */
|
|
92
|
+
static withSolidFill(color: ColorInput, options?: ProgressOptions): ProgressModel;
|
|
93
|
+
/** Unique ID for message routing. */
|
|
94
|
+
id(): number;
|
|
95
|
+
/** Current animated percent (0-1). */
|
|
96
|
+
percent(): number;
|
|
97
|
+
/** Target percent (0-1). */
|
|
98
|
+
targetPercent(): number;
|
|
99
|
+
/** Tea init hook (no-op). */
|
|
100
|
+
init(): Cmd<Msg>;
|
|
101
|
+
/** Handle messages; consumes FrameMsg for animation. */
|
|
102
|
+
update(msg: Msg): [ProgressModel, Cmd<Msg>];
|
|
103
|
+
/** Set a new target percent and start animation. */
|
|
104
|
+
setPercent(percent: number): [ProgressModel, Cmd<Msg>];
|
|
105
|
+
/** Increment the target percent. */
|
|
106
|
+
incrPercent(delta: number): [ProgressModel, Cmd<Msg>];
|
|
107
|
+
/** Update the spring configuration (keeps current state). */
|
|
108
|
+
setSpringOptions(frequency: number, damping: number): ProgressModel;
|
|
109
|
+
/** Render the animated progress bar. */
|
|
110
|
+
view(): string;
|
|
111
|
+
/** Render the bar at an explicit percent (0-1). */
|
|
112
|
+
viewAs(percent: number): string;
|
|
113
|
+
private renderGradient;
|
|
114
|
+
private renderSolid;
|
|
115
|
+
private renderEmpty;
|
|
116
|
+
private nextFrame;
|
|
117
|
+
private withState;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Message indicating a progress animation frame should be rendered.
|
|
122
|
+
* @public
|
|
123
|
+
*/
|
|
124
|
+
declare class FrameMsg {
|
|
125
|
+
/** Unique progress ID for routing */
|
|
126
|
+
readonly id: number;
|
|
127
|
+
/** Internal tag to prevent duplicate ticks */
|
|
128
|
+
readonly tag: number;
|
|
129
|
+
/** Timestamp when the frame was scheduled */
|
|
130
|
+
readonly time: Date;
|
|
131
|
+
readonly _tag = "progress:frame";
|
|
132
|
+
constructor(
|
|
133
|
+
/** Unique progress ID for routing */
|
|
134
|
+
id: number,
|
|
135
|
+
/** Internal tag to prevent duplicate ticks */
|
|
136
|
+
tag: number,
|
|
137
|
+
/** Timestamp when the frame was scheduled */
|
|
138
|
+
time: Date);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { FrameMsg, ProgressModel, type ProgressOptions };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { tick } from '@boba-cli/tea';
|
|
2
|
+
import { createDefaultContext, Style, resolveColor, width } from '@boba-cli/chapstick';
|
|
3
|
+
|
|
4
|
+
// src/model.ts
|
|
5
|
+
|
|
6
|
+
// src/messages.ts
|
|
7
|
+
var FrameMsg = class {
|
|
8
|
+
constructor(id, tag, time) {
|
|
9
|
+
this.id = id;
|
|
10
|
+
this.tag = tag;
|
|
11
|
+
this.time = time;
|
|
12
|
+
}
|
|
13
|
+
_tag = "progress:frame";
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/spring.ts
|
|
17
|
+
var Spring = class _Spring {
|
|
18
|
+
frequency;
|
|
19
|
+
damping;
|
|
20
|
+
#angular;
|
|
21
|
+
#pos;
|
|
22
|
+
#vel;
|
|
23
|
+
constructor(config = {}) {
|
|
24
|
+
this.frequency = config.frequency ?? 18;
|
|
25
|
+
this.damping = config.damping ?? 1;
|
|
26
|
+
this.#angular = this.frequency;
|
|
27
|
+
this.#pos = config.position ?? 0;
|
|
28
|
+
this.#vel = config.velocity ?? 0;
|
|
29
|
+
}
|
|
30
|
+
/** Current position. */
|
|
31
|
+
position() {
|
|
32
|
+
return this.#pos;
|
|
33
|
+
}
|
|
34
|
+
/** Current velocity. */
|
|
35
|
+
velocity() {
|
|
36
|
+
return this.#vel;
|
|
37
|
+
}
|
|
38
|
+
/** Return a copy with new spring options, keeping state. */
|
|
39
|
+
withOptions(frequency, damping) {
|
|
40
|
+
return new _Spring({
|
|
41
|
+
frequency,
|
|
42
|
+
damping,
|
|
43
|
+
position: this.#pos,
|
|
44
|
+
velocity: this.#vel
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Integrate toward target over the provided timestep (ms).
|
|
49
|
+
* Returns the new position and velocity.
|
|
50
|
+
*/
|
|
51
|
+
update(target, deltaMs) {
|
|
52
|
+
const dt = Math.min(0.05, Math.max(0, deltaMs / 1e3));
|
|
53
|
+
const displacement = this.#pos - target;
|
|
54
|
+
const springForce = -this.#angular * this.#angular * displacement;
|
|
55
|
+
const dampingForce = -2 * this.damping * this.#angular * this.#vel;
|
|
56
|
+
const acceleration = springForce + dampingForce;
|
|
57
|
+
const velocity = this.#vel + acceleration * dt;
|
|
58
|
+
const position = this.#pos + velocity * dt;
|
|
59
|
+
return new _Spring({
|
|
60
|
+
frequency: this.frequency,
|
|
61
|
+
damping: this.damping,
|
|
62
|
+
position,
|
|
63
|
+
velocity
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// src/gradient.ts
|
|
69
|
+
function clamp01(value) {
|
|
70
|
+
return Math.min(1, Math.max(0, value));
|
|
71
|
+
}
|
|
72
|
+
function hexToRgb(hex) {
|
|
73
|
+
const normalized = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
74
|
+
if (normalized.length !== 6) return null;
|
|
75
|
+
const int = Number.parseInt(normalized, 16);
|
|
76
|
+
if (Number.isNaN(int)) return null;
|
|
77
|
+
return {
|
|
78
|
+
r: int >> 16 & 255,
|
|
79
|
+
g: int >> 8 & 255,
|
|
80
|
+
b: int & 255
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function rgbToHex({ r, g, b }) {
|
|
84
|
+
const toHex = (v) => v.toString(16).padStart(2, "0");
|
|
85
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
86
|
+
}
|
|
87
|
+
function interpolateColor(colorA, colorB, t) {
|
|
88
|
+
const a = hexToRgb(colorA);
|
|
89
|
+
const b = hexToRgb(colorB);
|
|
90
|
+
if (!a || !b) return colorA;
|
|
91
|
+
const tClamped = clamp01(t);
|
|
92
|
+
const mix = (start, end) => Math.round(start + (end - start) * tClamped);
|
|
93
|
+
return rgbToHex({
|
|
94
|
+
r: mix(a.r, b.r),
|
|
95
|
+
g: mix(a.g, b.g),
|
|
96
|
+
b: mix(a.b, b.b)
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/model.ts
|
|
101
|
+
var FPS = 60;
|
|
102
|
+
var FRAME_MS = Math.round(1e3 / FPS);
|
|
103
|
+
var DEFAULT_ENV = createDefaultContext().env;
|
|
104
|
+
var DEFAULT_WIDTH = 40;
|
|
105
|
+
var DEFAULT_FULL = "\u2588";
|
|
106
|
+
var DEFAULT_EMPTY = "\u2591";
|
|
107
|
+
var DEFAULT_FULL_COLOR = "#7571F9";
|
|
108
|
+
var DEFAULT_EMPTY_COLOR = "#606060";
|
|
109
|
+
var DEFAULT_PERCENT_FORMAT = " %3.0f%%";
|
|
110
|
+
var DEFAULT_GRADIENT_START = "#5A56E0";
|
|
111
|
+
var DEFAULT_GRADIENT_END = "#EE6FF8";
|
|
112
|
+
var SETTLE_DISTANCE = 2e-3;
|
|
113
|
+
var SETTLE_VELOCITY = 0.01;
|
|
114
|
+
var lastId = 0;
|
|
115
|
+
function nextId() {
|
|
116
|
+
return ++lastId;
|
|
117
|
+
}
|
|
118
|
+
function clamp012(value) {
|
|
119
|
+
if (Number.isNaN(value)) return 0;
|
|
120
|
+
return Math.max(0, Math.min(1, value));
|
|
121
|
+
}
|
|
122
|
+
function ensureChar(input, fallback) {
|
|
123
|
+
if (!input) return fallback;
|
|
124
|
+
return input.slice(0, 1);
|
|
125
|
+
}
|
|
126
|
+
function formatPercent(value, fmt) {
|
|
127
|
+
const percentValue = clamp012(value) * 100;
|
|
128
|
+
const match = fmt.match(/%(\d+)?(?:\.(\d+))?f/);
|
|
129
|
+
if (!match) {
|
|
130
|
+
return `${percentValue.toFixed(0)}%`;
|
|
131
|
+
}
|
|
132
|
+
const precision = match[2] ? Number.parseInt(match[2], 10) : 0;
|
|
133
|
+
const replacement = percentValue.toFixed(precision);
|
|
134
|
+
return fmt.replace(/%(\d+)?(?:\.(\d+))?f/, replacement).replace(/%%/g, "%");
|
|
135
|
+
}
|
|
136
|
+
function settle(percent, target, velocity) {
|
|
137
|
+
const dist = Math.abs(percent - target);
|
|
138
|
+
return dist < SETTLE_DISTANCE && Math.abs(velocity) < SETTLE_VELOCITY;
|
|
139
|
+
}
|
|
140
|
+
function resolvedColor(color, fallback) {
|
|
141
|
+
return resolveColor(color, DEFAULT_ENV) ?? fallback;
|
|
142
|
+
}
|
|
143
|
+
function defaultState() {
|
|
144
|
+
return {
|
|
145
|
+
percent: 0,
|
|
146
|
+
target: 0,
|
|
147
|
+
velocity: 0,
|
|
148
|
+
id: nextId(),
|
|
149
|
+
tag: 0,
|
|
150
|
+
spring: new Spring(),
|
|
151
|
+
lastFrameTime: null
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
var ProgressModel = class _ProgressModel {
|
|
155
|
+
width;
|
|
156
|
+
full;
|
|
157
|
+
empty;
|
|
158
|
+
fullColor;
|
|
159
|
+
emptyColor;
|
|
160
|
+
showPercentage;
|
|
161
|
+
percentFormat;
|
|
162
|
+
gradientStart;
|
|
163
|
+
gradientEnd;
|
|
164
|
+
scaleGradient;
|
|
165
|
+
useGradient;
|
|
166
|
+
percentageStyle;
|
|
167
|
+
#percent;
|
|
168
|
+
#target;
|
|
169
|
+
#velocity;
|
|
170
|
+
#id;
|
|
171
|
+
#tag;
|
|
172
|
+
#spring;
|
|
173
|
+
#lastFrameTime;
|
|
174
|
+
constructor(options = {}, state = {}) {
|
|
175
|
+
this.width = options.width ?? DEFAULT_WIDTH;
|
|
176
|
+
this.full = ensureChar(options.full, DEFAULT_FULL);
|
|
177
|
+
this.empty = ensureChar(options.empty, DEFAULT_EMPTY);
|
|
178
|
+
this.fullColor = resolvedColor(options.fullColor, DEFAULT_FULL_COLOR);
|
|
179
|
+
this.emptyColor = resolvedColor(options.emptyColor, DEFAULT_EMPTY_COLOR);
|
|
180
|
+
this.showPercentage = options.showPercentage ?? true;
|
|
181
|
+
this.percentFormat = options.percentFormat ?? DEFAULT_PERCENT_FORMAT;
|
|
182
|
+
this.percentageStyle = options.percentageStyle ?? new Style();
|
|
183
|
+
const start = options.gradientStart ? resolveColor(options.gradientStart, DEFAULT_ENV) : void 0;
|
|
184
|
+
const end = options.gradientEnd ? resolveColor(options.gradientEnd, DEFAULT_ENV) : void 0;
|
|
185
|
+
this.gradientStart = start;
|
|
186
|
+
this.gradientEnd = end;
|
|
187
|
+
this.scaleGradient = options.scaleGradient ?? false;
|
|
188
|
+
this.useGradient = Boolean(start && end);
|
|
189
|
+
const frequency = options.springFrequency ?? state.spring?.frequency ?? 18;
|
|
190
|
+
const damping = options.springDamping ?? state.spring?.damping ?? 1;
|
|
191
|
+
const baseState = { ...defaultState(), ...state };
|
|
192
|
+
const spring = state.spring ?? new Spring({ frequency, damping });
|
|
193
|
+
this.#spring = spring.withOptions(frequency, damping);
|
|
194
|
+
this.#percent = clamp012(baseState.percent);
|
|
195
|
+
this.#target = clamp012(baseState.target);
|
|
196
|
+
this.#velocity = baseState.velocity;
|
|
197
|
+
this.#id = baseState.id;
|
|
198
|
+
this.#tag = baseState.tag;
|
|
199
|
+
this.#lastFrameTime = baseState.lastFrameTime;
|
|
200
|
+
}
|
|
201
|
+
/** Create a new progress bar with defaults. */
|
|
202
|
+
static new(options = {}) {
|
|
203
|
+
return new _ProgressModel(options);
|
|
204
|
+
}
|
|
205
|
+
/** Convenience constructor with default gradient. */
|
|
206
|
+
static withDefaultGradient(options = {}) {
|
|
207
|
+
return new _ProgressModel({
|
|
208
|
+
...options,
|
|
209
|
+
gradientStart: options.gradientStart ?? DEFAULT_GRADIENT_START,
|
|
210
|
+
gradientEnd: options.gradientEnd ?? DEFAULT_GRADIENT_END
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
/** Convenience constructor with a custom gradient. */
|
|
214
|
+
static withGradient(colorA, colorB, options = {}) {
|
|
215
|
+
return new _ProgressModel({
|
|
216
|
+
...options,
|
|
217
|
+
gradientStart: colorA,
|
|
218
|
+
gradientEnd: colorB
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
/** Convenience constructor with solid fill. */
|
|
222
|
+
static withSolidFill(color, options = {}) {
|
|
223
|
+
return new _ProgressModel({
|
|
224
|
+
...options,
|
|
225
|
+
fullColor: color,
|
|
226
|
+
gradientStart: void 0,
|
|
227
|
+
gradientEnd: void 0
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
/** Unique ID for message routing. */
|
|
231
|
+
id() {
|
|
232
|
+
return this.#id;
|
|
233
|
+
}
|
|
234
|
+
/** Current animated percent (0-1). */
|
|
235
|
+
percent() {
|
|
236
|
+
return clamp012(this.#percent);
|
|
237
|
+
}
|
|
238
|
+
/** Target percent (0-1). */
|
|
239
|
+
targetPercent() {
|
|
240
|
+
return clamp012(this.#target);
|
|
241
|
+
}
|
|
242
|
+
/** Tea init hook (no-op). */
|
|
243
|
+
init() {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
/** Handle messages; consumes FrameMsg for animation. */
|
|
247
|
+
update(msg) {
|
|
248
|
+
if (!(msg instanceof FrameMsg)) {
|
|
249
|
+
return [this, null];
|
|
250
|
+
}
|
|
251
|
+
if (msg.id !== this.#id || msg.tag !== this.#tag) {
|
|
252
|
+
return [this, null];
|
|
253
|
+
}
|
|
254
|
+
const dt = this.#lastFrameTime === null ? FRAME_MS : Math.max(1, msg.time.getTime() - this.#lastFrameTime.getTime());
|
|
255
|
+
const spring = this.#spring.update(this.#target, dt);
|
|
256
|
+
const nextPercent = clamp012(spring.position());
|
|
257
|
+
const nextVelocity = spring.velocity();
|
|
258
|
+
const next = this.withState({
|
|
259
|
+
percent: nextPercent,
|
|
260
|
+
velocity: nextVelocity,
|
|
261
|
+
tag: this.#tag,
|
|
262
|
+
spring,
|
|
263
|
+
lastFrameTime: msg.time
|
|
264
|
+
});
|
|
265
|
+
if (settle(nextPercent, this.#target, nextVelocity)) {
|
|
266
|
+
return [next, null];
|
|
267
|
+
}
|
|
268
|
+
return [next, next.nextFrame()];
|
|
269
|
+
}
|
|
270
|
+
/** Set a new target percent and start animation. */
|
|
271
|
+
setPercent(percent) {
|
|
272
|
+
const clamped = clamp012(percent);
|
|
273
|
+
const next = this.withState({
|
|
274
|
+
target: clamped,
|
|
275
|
+
tag: this.#tag + 1,
|
|
276
|
+
lastFrameTime: null
|
|
277
|
+
});
|
|
278
|
+
return [next, next.nextFrame()];
|
|
279
|
+
}
|
|
280
|
+
/** Increment the target percent. */
|
|
281
|
+
incrPercent(delta) {
|
|
282
|
+
return this.setPercent(this.#target + delta);
|
|
283
|
+
}
|
|
284
|
+
/** Update the spring configuration (keeps current state). */
|
|
285
|
+
setSpringOptions(frequency, damping) {
|
|
286
|
+
const spring = this.#spring.withOptions(frequency, damping);
|
|
287
|
+
return this.withState({ spring });
|
|
288
|
+
}
|
|
289
|
+
/** Render the animated progress bar. */
|
|
290
|
+
view() {
|
|
291
|
+
return this.viewAs(this.percent());
|
|
292
|
+
}
|
|
293
|
+
/** Render the bar at an explicit percent (0-1). */
|
|
294
|
+
viewAs(percent) {
|
|
295
|
+
const pct = clamp012(percent);
|
|
296
|
+
const percentText = this.showPercentage ? this.percentageStyle.render(formatPercent(pct, this.percentFormat)) : "";
|
|
297
|
+
const percentWidth = this.showPercentage ? width(percentText) : 0;
|
|
298
|
+
const totalBarWidth = Math.max(0, this.width - percentWidth);
|
|
299
|
+
const filledWidth = Math.max(0, Math.round(totalBarWidth * pct));
|
|
300
|
+
const emptyWidth = Math.max(0, totalBarWidth - filledWidth);
|
|
301
|
+
const bar = `${this.useGradient ? this.renderGradient(filledWidth, totalBarWidth) : this.renderSolid(filledWidth)}${this.renderEmpty(emptyWidth)}`;
|
|
302
|
+
return `${bar}${percentText}`;
|
|
303
|
+
}
|
|
304
|
+
renderGradient(filledWidth, totalWidth) {
|
|
305
|
+
if (!this.useGradient || filledWidth <= 0 || !this.gradientStart || !this.gradientEnd) {
|
|
306
|
+
return "";
|
|
307
|
+
}
|
|
308
|
+
const parts = [];
|
|
309
|
+
const denominator = this.scaleGradient ? Math.max(1, filledWidth - 1) : Math.max(1, totalWidth - 1);
|
|
310
|
+
for (let i = 0; i < filledWidth; i++) {
|
|
311
|
+
const t = filledWidth === 1 ? 0.5 : i / denominator;
|
|
312
|
+
const color = interpolateColor(this.gradientStart, this.gradientEnd, t);
|
|
313
|
+
parts.push(new Style().foreground(color).render(this.full));
|
|
314
|
+
}
|
|
315
|
+
return parts.join("");
|
|
316
|
+
}
|
|
317
|
+
renderSolid(filledWidth) {
|
|
318
|
+
if (filledWidth <= 0) return "";
|
|
319
|
+
const styled = new Style().foreground(this.fullColor).render(this.full);
|
|
320
|
+
return styled.repeat(filledWidth);
|
|
321
|
+
}
|
|
322
|
+
renderEmpty(emptyWidth) {
|
|
323
|
+
if (emptyWidth <= 0) return "";
|
|
324
|
+
const styled = new Style().foreground(this.emptyColor).render(this.empty);
|
|
325
|
+
return styled.repeat(emptyWidth);
|
|
326
|
+
}
|
|
327
|
+
nextFrame() {
|
|
328
|
+
const id = this.#id;
|
|
329
|
+
const tag = this.#tag;
|
|
330
|
+
return tick(FRAME_MS, (time) => new FrameMsg(id, tag, time));
|
|
331
|
+
}
|
|
332
|
+
withState(state) {
|
|
333
|
+
return new _ProgressModel(
|
|
334
|
+
{
|
|
335
|
+
width: this.width,
|
|
336
|
+
full: this.full,
|
|
337
|
+
empty: this.empty,
|
|
338
|
+
fullColor: this.fullColor,
|
|
339
|
+
emptyColor: this.emptyColor,
|
|
340
|
+
showPercentage: this.showPercentage,
|
|
341
|
+
percentFormat: this.percentFormat,
|
|
342
|
+
gradientStart: this.gradientStart,
|
|
343
|
+
gradientEnd: this.gradientEnd,
|
|
344
|
+
scaleGradient: this.scaleGradient,
|
|
345
|
+
springFrequency: this.#spring.frequency,
|
|
346
|
+
springDamping: this.#spring.damping,
|
|
347
|
+
percentageStyle: this.percentageStyle
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
percent: this.#percent,
|
|
351
|
+
target: this.#target,
|
|
352
|
+
velocity: this.#velocity,
|
|
353
|
+
id: this.#id,
|
|
354
|
+
tag: this.#tag,
|
|
355
|
+
spring: this.#spring,
|
|
356
|
+
lastFrameTime: this.#lastFrameTime,
|
|
357
|
+
...state
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
export { FrameMsg, ProgressModel };
|
|
364
|
+
//# sourceMappingURL=index.js.map
|
|
365
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/messages.ts","../src/spring.ts","../src/gradient.ts","../src/model.ts"],"names":["clamp01","textWidth"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Message indicating a progress animation frame should be rendered.\n * @public\n */\nexport class FrameMsg {\n readonly _tag = 'progress:frame'\n\n constructor(\n /** Unique progress ID for routing */\n public readonly id: number,\n /** Internal tag to prevent duplicate ticks */\n public readonly tag: number,\n /** Timestamp when the frame was scheduled */\n public readonly time: Date,\n ) {}\n}\n","interface SpringConfig {\n /** Oscillation speed (Hz). */\n frequency?: number\n /** Damping factor (1.0 = critical-ish). */\n damping?: number\n /** Starting position (0-1). */\n position?: number\n /** Starting velocity. */\n velocity?: number\n}\n\n/**\n * Minimal damped spring integrator (ported from harmonica).\n * Stores its own position/velocity and integrates using a simple\n * damped harmonic oscillator step.\n */\nexport class Spring {\n readonly frequency: number\n readonly damping: number\n readonly #angular: number\n readonly #pos: number\n readonly #vel: number\n\n constructor(config: SpringConfig = {}) {\n this.frequency = config.frequency ?? 18\n this.damping = config.damping ?? 1\n // Note: using the provided frequency directly (not 2π) keeps the\n // explicit Euler step stable at ~60 FPS for our use case.\n this.#angular = this.frequency\n this.#pos = config.position ?? 0\n this.#vel = config.velocity ?? 0\n }\n\n /** Current position. */\n position(): number {\n return this.#pos\n }\n\n /** Current velocity. */\n velocity(): number {\n return this.#vel\n }\n\n /** Return a copy with new spring options, keeping state. */\n withOptions(frequency: number, damping: number): Spring {\n return new Spring({\n frequency,\n damping,\n position: this.#pos,\n velocity: this.#vel,\n })\n }\n\n /**\n * Integrate toward target over the provided timestep (ms).\n * Returns the new position and velocity.\n */\n update(target: number, deltaMs: number): Spring {\n // Clamp dt to avoid instability on slow frames.\n const dt = Math.min(0.05, Math.max(0, deltaMs / 1000))\n const displacement = this.#pos - target\n\n const springForce = -this.#angular * this.#angular * displacement\n const dampingForce = -2 * this.damping * this.#angular * this.#vel\n const acceleration = springForce + dampingForce\n\n const velocity = this.#vel + acceleration * dt\n const position = this.#pos + velocity * dt\n\n return new Spring({\n frequency: this.frequency,\n damping: this.damping,\n position,\n velocity,\n })\n }\n}\n","interface RGB {\n r: number\n g: number\n b: number\n}\n\nfunction clamp01(value: number): number {\n return Math.min(1, Math.max(0, value))\n}\n\nfunction hexToRgb(hex: string): RGB | null {\n const normalized = hex.startsWith('#') ? hex.slice(1) : hex\n if (normalized.length !== 6) return null\n const int = Number.parseInt(normalized, 16)\n if (Number.isNaN(int)) return null\n return {\n r: (int >> 16) & 0xff,\n g: (int >> 8) & 0xff,\n b: int & 0xff,\n }\n}\n\nfunction rgbToHex({ r, g, b }: RGB): string {\n const toHex = (v: number) => v.toString(16).padStart(2, '0')\n return `#${toHex(r)}${toHex(g)}${toHex(b)}`\n}\n\n/**\n * Linearly interpolate between two hex colors in RGB space.\n * Returns the first color if parsing fails.\n */\nexport function interpolateColor(\n colorA: string,\n colorB: string,\n t: number,\n): string {\n const a = hexToRgb(colorA)\n const b = hexToRgb(colorB)\n if (!a || !b) return colorA\n\n const tClamped = clamp01(t)\n const mix = (start: number, end: number) =>\n Math.round(start + (end - start) * tClamped)\n\n return rgbToHex({\n r: mix(a.r, b.r),\n g: mix(a.g, b.g),\n b: mix(a.b, b.b),\n })\n}\n","import { tick, type Cmd, type Msg } from '@boba-cli/tea'\nimport {\n Style,\n createDefaultContext,\n resolveColor,\n width as textWidth,\n type ColorInput,\n} from '@boba-cli/chapstick'\nimport { FrameMsg } from './messages.js'\nimport { Spring } from './spring.js'\nimport { interpolateColor } from './gradient.js'\n\nconst FPS = 60\nconst FRAME_MS = Math.round(1000 / FPS)\nconst DEFAULT_ENV = createDefaultContext().env\nconst DEFAULT_WIDTH = 40\nconst DEFAULT_FULL = '█'\nconst DEFAULT_EMPTY = '░'\nconst DEFAULT_FULL_COLOR = '#7571F9'\nconst DEFAULT_EMPTY_COLOR = '#606060'\nconst DEFAULT_PERCENT_FORMAT = ' %3.0f%%'\nconst DEFAULT_GRADIENT_START = '#5A56E0'\nconst DEFAULT_GRADIENT_END = '#EE6FF8'\nconst SETTLE_DISTANCE = 0.002\nconst SETTLE_VELOCITY = 0.01\n\n// Module-level ID counter for message routing\nlet lastId = 0\nfunction nextId(): number {\n return ++lastId\n}\n\nfunction clamp01(value: number): number {\n if (Number.isNaN(value)) return 0\n return Math.max(0, Math.min(1, value))\n}\n\nfunction ensureChar(input: string | undefined, fallback: string): string {\n if (!input) return fallback\n // Use the first Unicode grapheme; for simplicity take first code unit\n return input.slice(0, 1)\n}\n\nfunction formatPercent(value: number, fmt: string): string {\n const percentValue = clamp01(value) * 100\n const match = fmt.match(/%(\\d+)?(?:\\.(\\d+))?f/)\n if (!match) {\n return `${percentValue.toFixed(0)}%`\n }\n const precision = match[2] ? Number.parseInt(match[2], 10) : 0\n const replacement = percentValue.toFixed(precision)\n return fmt.replace(/%(\\d+)?(?:\\.(\\d+))?f/, replacement).replace(/%%/g, '%')\n}\n\nfunction settle(percent: number, target: number, velocity: number): boolean {\n const dist = Math.abs(percent - target)\n return dist < SETTLE_DISTANCE && Math.abs(velocity) < SETTLE_VELOCITY\n}\n\nfunction resolvedColor(\n color: ColorInput | undefined,\n fallback: string,\n): string {\n return resolveColor(color, DEFAULT_ENV) ?? fallback\n}\n\n/**\n * Options for the progress bar model.\n * @public\n */\nexport interface ProgressOptions {\n width?: number\n full?: string\n empty?: string\n fullColor?: ColorInput\n emptyColor?: ColorInput\n showPercentage?: boolean\n percentFormat?: string\n gradientStart?: ColorInput\n gradientEnd?: ColorInput\n scaleGradient?: boolean\n springFrequency?: number\n springDamping?: number\n percentageStyle?: Style\n}\n\ninterface ProgressState {\n percent: number\n target: number\n velocity: number\n id: number\n tag: number\n spring: Spring\n lastFrameTime: Date | null\n}\n\ntype ProgressInit = Partial<ProgressState>\n\nfunction defaultState(): ProgressState {\n return {\n percent: 0,\n target: 0,\n velocity: 0,\n id: nextId(),\n tag: 0,\n spring: new Spring(),\n lastFrameTime: null,\n }\n}\n\n/**\n * Animated progress bar model with spring-based easing.\n * @public\n */\nexport class ProgressModel {\n readonly width: number\n readonly full: string\n readonly empty: string\n readonly fullColor: string\n readonly emptyColor: string\n readonly showPercentage: boolean\n readonly percentFormat: string\n readonly gradientStart?: string\n readonly gradientEnd?: string\n readonly scaleGradient: boolean\n readonly useGradient: boolean\n readonly percentageStyle: Style\n\n readonly #percent: number\n readonly #target: number\n readonly #velocity: number\n readonly #id: number\n readonly #tag: number\n readonly #spring: Spring\n readonly #lastFrameTime: Date | null\n\n constructor(options: ProgressOptions = {}, state: ProgressInit = {}) {\n this.width = options.width ?? DEFAULT_WIDTH\n this.full = ensureChar(options.full, DEFAULT_FULL)\n this.empty = ensureChar(options.empty, DEFAULT_EMPTY)\n this.fullColor = resolvedColor(options.fullColor, DEFAULT_FULL_COLOR)\n this.emptyColor = resolvedColor(options.emptyColor, DEFAULT_EMPTY_COLOR)\n this.showPercentage = options.showPercentage ?? true\n this.percentFormat = options.percentFormat ?? DEFAULT_PERCENT_FORMAT\n this.percentageStyle = options.percentageStyle ?? new Style()\n\n const start = options.gradientStart\n ? resolveColor(options.gradientStart, DEFAULT_ENV)\n : undefined\n const end = options.gradientEnd\n ? resolveColor(options.gradientEnd, DEFAULT_ENV)\n : undefined\n this.gradientStart = start\n this.gradientEnd = end\n this.scaleGradient = options.scaleGradient ?? false\n this.useGradient = Boolean(start && end)\n\n const frequency = options.springFrequency ?? state.spring?.frequency ?? 18\n const damping = options.springDamping ?? state.spring?.damping ?? 1\n const baseState = { ...defaultState(), ...state }\n const spring = state.spring ?? new Spring({ frequency, damping })\n this.#spring = spring.withOptions(frequency, damping)\n\n this.#percent = clamp01(baseState.percent)\n this.#target = clamp01(baseState.target)\n this.#velocity = baseState.velocity\n this.#id = baseState.id\n this.#tag = baseState.tag\n this.#lastFrameTime = baseState.lastFrameTime\n }\n\n /** Create a new progress bar with defaults. */\n static new(options: ProgressOptions = {}): ProgressModel {\n return new ProgressModel(options)\n }\n\n /** Convenience constructor with default gradient. */\n static withDefaultGradient(options: ProgressOptions = {}): ProgressModel {\n return new ProgressModel({\n ...options,\n gradientStart: options.gradientStart ?? DEFAULT_GRADIENT_START,\n gradientEnd: options.gradientEnd ?? DEFAULT_GRADIENT_END,\n })\n }\n\n /** Convenience constructor with a custom gradient. */\n static withGradient(\n colorA: ColorInput,\n colorB: ColorInput,\n options: ProgressOptions = {},\n ): ProgressModel {\n return new ProgressModel({\n ...options,\n gradientStart: colorA,\n gradientEnd: colorB,\n })\n }\n\n /** Convenience constructor with solid fill. */\n static withSolidFill(\n color: ColorInput,\n options: ProgressOptions = {},\n ): ProgressModel {\n return new ProgressModel({\n ...options,\n fullColor: color,\n gradientStart: undefined,\n gradientEnd: undefined,\n })\n }\n\n /** Unique ID for message routing. */\n id(): number {\n return this.#id\n }\n\n /** Current animated percent (0-1). */\n percent(): number {\n return clamp01(this.#percent)\n }\n\n /** Target percent (0-1). */\n targetPercent(): number {\n return clamp01(this.#target)\n }\n\n /** Tea init hook (no-op). */\n init(): Cmd<Msg> {\n return null\n }\n\n /** Handle messages; consumes FrameMsg for animation. */\n update(msg: Msg): [ProgressModel, Cmd<Msg>] {\n if (!(msg instanceof FrameMsg)) {\n return [this, null]\n }\n if (msg.id !== this.#id || msg.tag !== this.#tag) {\n return [this, null]\n }\n\n const dt =\n this.#lastFrameTime === null\n ? FRAME_MS\n : Math.max(1, msg.time.getTime() - this.#lastFrameTime.getTime())\n\n const spring = this.#spring.update(this.#target, dt)\n const nextPercent = clamp01(spring.position())\n const nextVelocity = spring.velocity()\n\n const next = this.withState({\n percent: nextPercent,\n velocity: nextVelocity,\n tag: this.#tag,\n spring,\n lastFrameTime: msg.time,\n })\n\n if (settle(nextPercent, this.#target, nextVelocity)) {\n return [next, null]\n }\n\n return [next, next.nextFrame()]\n }\n\n /** Set a new target percent and start animation. */\n setPercent(percent: number): [ProgressModel, Cmd<Msg>] {\n const clamped = clamp01(percent)\n const next = this.withState({\n target: clamped,\n tag: this.#tag + 1,\n lastFrameTime: null,\n })\n return [next, next.nextFrame()]\n }\n\n /** Increment the target percent. */\n incrPercent(delta: number): [ProgressModel, Cmd<Msg>] {\n return this.setPercent(this.#target + delta)\n }\n\n /** Update the spring configuration (keeps current state). */\n setSpringOptions(frequency: number, damping: number): ProgressModel {\n const spring = this.#spring.withOptions(frequency, damping)\n return this.withState({ spring })\n }\n\n /** Render the animated progress bar. */\n view(): string {\n return this.viewAs(this.percent())\n }\n\n /** Render the bar at an explicit percent (0-1). */\n viewAs(percent: number): string {\n const pct = clamp01(percent)\n const percentText = this.showPercentage\n ? this.percentageStyle.render(formatPercent(pct, this.percentFormat))\n : ''\n\n const percentWidth = this.showPercentage ? textWidth(percentText) : 0\n const totalBarWidth = Math.max(0, this.width - percentWidth)\n const filledWidth = Math.max(0, Math.round(totalBarWidth * pct))\n const emptyWidth = Math.max(0, totalBarWidth - filledWidth)\n\n const bar = `${this.useGradient ? this.renderGradient(filledWidth, totalBarWidth) : this.renderSolid(filledWidth)}${this.renderEmpty(emptyWidth)}`\n\n return `${bar}${percentText}`\n }\n\n private renderGradient(filledWidth: number, totalWidth: number): string {\n if (\n !this.useGradient ||\n filledWidth <= 0 ||\n !this.gradientStart ||\n !this.gradientEnd\n ) {\n return ''\n }\n\n const parts: string[] = []\n const denominator = this.scaleGradient\n ? Math.max(1, filledWidth - 1)\n : Math.max(1, totalWidth - 1)\n\n for (let i = 0; i < filledWidth; i++) {\n const t = filledWidth === 1 ? 0.5 : i / denominator\n const color = interpolateColor(this.gradientStart, this.gradientEnd, t)\n parts.push(new Style().foreground(color).render(this.full))\n }\n\n return parts.join('')\n }\n\n private renderSolid(filledWidth: number): string {\n if (filledWidth <= 0) return ''\n const styled = new Style().foreground(this.fullColor).render(this.full)\n return styled.repeat(filledWidth)\n }\n\n private renderEmpty(emptyWidth: number): string {\n if (emptyWidth <= 0) return ''\n const styled = new Style().foreground(this.emptyColor).render(this.empty)\n return styled.repeat(emptyWidth)\n }\n\n private nextFrame(): Cmd<FrameMsg> {\n const id = this.#id\n const tag = this.#tag\n return tick(FRAME_MS, (time) => new FrameMsg(id, tag, time))\n }\n\n private withState(state: ProgressInit): ProgressModel {\n return new ProgressModel(\n {\n width: this.width,\n full: this.full,\n empty: this.empty,\n fullColor: this.fullColor,\n emptyColor: this.emptyColor,\n showPercentage: this.showPercentage,\n percentFormat: this.percentFormat,\n gradientStart: this.gradientStart,\n gradientEnd: this.gradientEnd,\n scaleGradient: this.scaleGradient,\n springFrequency: this.#spring.frequency,\n springDamping: this.#spring.damping,\n percentageStyle: this.percentageStyle,\n },\n {\n percent: this.#percent,\n target: this.#target,\n velocity: this.#velocity,\n id: this.#id,\n tag: this.#tag,\n spring: this.#spring,\n lastFrameTime: this.#lastFrameTime,\n ...state,\n },\n )\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@boba-cli/progress",
|
|
3
|
+
"description": "Animated progress bar for Boba terminal UIs",
|
|
4
|
+
"version": "0.1.0-alpha.2",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"@boba-cli/chapstick": "0.1.0-alpha.2",
|
|
7
|
+
"@boba-cli/tea": "0.1.0-alpha.1"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"typescript": "5.8.2",
|
|
11
|
+
"vitest": "^4.0.16"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20.0.0"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"import": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"require": {
|
|
23
|
+
"types": "./dist/index.d.cts",
|
|
24
|
+
"default": "./dist/index.cjs"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"main": "./dist/index.cjs",
|
|
33
|
+
"module": "./dist/index.js",
|
|
34
|
+
"type": "module",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"check:api-report": "pnpm run generate:api-report",
|
|
39
|
+
"check:eslint": "pnpm run lint",
|
|
40
|
+
"generate:api-report": "api-extractor run --local",
|
|
41
|
+
"lint": "eslint \"{src,test}/**/*.{ts,tsx}\"",
|
|
42
|
+
"test": "vitest run"
|
|
43
|
+
}
|
|
44
|
+
}
|