@boba-cli/progress 0.1.0-alpha.2 → 0.1.0-alpha.4
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 +27 -6
- package/dist/index.cjs +20 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +21 -8
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Animated progress bar for Boba terminal UIs. Port of Charmbracelet Bubbles progress.
|
|
4
4
|
|
|
5
|
-
<img src="../../examples/progress
|
|
5
|
+
<img src="../../examples/animations/progress.gif" width="950" alt="Progress component demo" />
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -10,7 +10,29 @@ Animated progress bar for Boba terminal UIs. Port of Charmbracelet Bubbles progr
|
|
|
10
10
|
pnpm add @boba-cli/progress
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Using with the DSL (Recommended)
|
|
14
|
+
|
|
15
|
+
The easiest way to use progress bars is through [`@boba-cli/dsl`](../dsl/README.md):
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createApp, progress, text, vstack } from '@boba-cli/dsl'
|
|
19
|
+
|
|
20
|
+
const app = createApp()
|
|
21
|
+
.state({ percent: 0 })
|
|
22
|
+
.component('bar', progress({ width: 40, gradient: true }))
|
|
23
|
+
.onInit(({ sendToComponent }) => {
|
|
24
|
+
sendToComponent('bar', (m) => m.setPercent(0.65))
|
|
25
|
+
})
|
|
26
|
+
.onKey('q', ({ quit }) => quit())
|
|
27
|
+
.view(({ components }) => vstack(components.bar, text('Loading...')))
|
|
28
|
+
.build()
|
|
29
|
+
|
|
30
|
+
await app.run()
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Low-Level Usage
|
|
34
|
+
|
|
35
|
+
For direct use with `@boba-cli/tea`:
|
|
14
36
|
|
|
15
37
|
```ts
|
|
16
38
|
import { ProgressModel, FrameMsg } from '@boba-cli/progress'
|
|
@@ -18,20 +40,19 @@ import type { Cmd, Msg } from '@boba-cli/tea'
|
|
|
18
40
|
|
|
19
41
|
let progress = ProgressModel.withDefaultGradient({ width: 30 })
|
|
20
42
|
|
|
21
|
-
|
|
22
|
-
// Start at 40%
|
|
43
|
+
init(): Cmd<Msg> {
|
|
23
44
|
const [next, cmd] = progress.setPercent(0.4)
|
|
24
45
|
progress = next
|
|
25
46
|
return cmd
|
|
26
47
|
}
|
|
27
48
|
|
|
28
|
-
|
|
49
|
+
update(msg: Msg): [unknown, Cmd<Msg>] {
|
|
29
50
|
const [next, cmd] = progress.update(msg)
|
|
30
51
|
progress = next
|
|
31
52
|
return [{ progress }, cmd]
|
|
32
53
|
}
|
|
33
54
|
|
|
34
|
-
|
|
55
|
+
view(): string {
|
|
35
56
|
return progress.view()
|
|
36
57
|
}
|
|
37
58
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -102,7 +102,6 @@ function interpolateColor(colorA, colorB, t) {
|
|
|
102
102
|
// src/model.ts
|
|
103
103
|
var FPS = 60;
|
|
104
104
|
var FRAME_MS = Math.round(1e3 / FPS);
|
|
105
|
-
var DEFAULT_ENV = chapstick.createDefaultContext().env;
|
|
106
105
|
var DEFAULT_WIDTH = 40;
|
|
107
106
|
var DEFAULT_FULL = "\u2588";
|
|
108
107
|
var DEFAULT_EMPTY = "\u2591";
|
|
@@ -139,8 +138,11 @@ function settle(percent, target, velocity) {
|
|
|
139
138
|
const dist = Math.abs(percent - target);
|
|
140
139
|
return dist < SETTLE_DISTANCE && Math.abs(velocity) < SETTLE_VELOCITY;
|
|
141
140
|
}
|
|
141
|
+
function getDefaultEnv() {
|
|
142
|
+
return chapstick.createDefaultContext().env;
|
|
143
|
+
}
|
|
142
144
|
function resolvedColor(color, fallback) {
|
|
143
|
-
return chapstick.resolveColor(color,
|
|
145
|
+
return chapstick.resolveColor(color, getDefaultEnv()) ?? fallback;
|
|
144
146
|
}
|
|
145
147
|
function defaultState() {
|
|
146
148
|
return {
|
|
@@ -182,8 +184,8 @@ var ProgressModel = class _ProgressModel {
|
|
|
182
184
|
this.showPercentage = options.showPercentage ?? true;
|
|
183
185
|
this.percentFormat = options.percentFormat ?? DEFAULT_PERCENT_FORMAT;
|
|
184
186
|
this.percentageStyle = options.percentageStyle ?? new chapstick.Style();
|
|
185
|
-
const start = options.gradientStart ? chapstick.resolveColor(options.gradientStart,
|
|
186
|
-
const end = options.gradientEnd ? chapstick.resolveColor(options.gradientEnd,
|
|
187
|
+
const start = options.gradientStart ? chapstick.resolveColor(options.gradientStart, getDefaultEnv()) : void 0;
|
|
188
|
+
const end = options.gradientEnd ? chapstick.resolveColor(options.gradientEnd, getDefaultEnv()) : void 0;
|
|
187
189
|
this.gradientStart = start;
|
|
188
190
|
this.gradientEnd = end;
|
|
189
191
|
this.scaleGradient = options.scaleGradient ?? false;
|
|
@@ -272,6 +274,10 @@ var ProgressModel = class _ProgressModel {
|
|
|
272
274
|
/** Set a new target percent and start animation. */
|
|
273
275
|
setPercent(percent) {
|
|
274
276
|
const clamped = clamp012(percent);
|
|
277
|
+
if (this.isAnimating()) {
|
|
278
|
+
const next2 = this.withState({ target: clamped });
|
|
279
|
+
return [next2, null];
|
|
280
|
+
}
|
|
275
281
|
const next = this.withState({
|
|
276
282
|
target: clamped,
|
|
277
283
|
tag: this.#tag + 1,
|
|
@@ -279,6 +285,10 @@ var ProgressModel = class _ProgressModel {
|
|
|
279
285
|
});
|
|
280
286
|
return [next, next.nextFrame()];
|
|
281
287
|
}
|
|
288
|
+
/** Returns true if animation is in progress (not yet settled). */
|
|
289
|
+
isAnimating() {
|
|
290
|
+
return !settle(this.#percent, this.#target, this.#velocity);
|
|
291
|
+
}
|
|
282
292
|
/** Increment the target percent. */
|
|
283
293
|
incrPercent(delta) {
|
|
284
294
|
return this.setPercent(this.#target + delta);
|
|
@@ -309,21 +319,24 @@ var ProgressModel = class _ProgressModel {
|
|
|
309
319
|
}
|
|
310
320
|
const parts = [];
|
|
311
321
|
const denominator = this.scaleGradient ? Math.max(1, filledWidth - 1) : Math.max(1, totalWidth - 1);
|
|
322
|
+
const ctx = chapstick.createDefaultContext();
|
|
312
323
|
for (let i = 0; i < filledWidth; i++) {
|
|
313
324
|
const t = filledWidth === 1 ? 0.5 : i / denominator;
|
|
314
325
|
const color = interpolateColor(this.gradientStart, this.gradientEnd, t);
|
|
315
|
-
parts.push(new chapstick.Style().foreground(color).render(this.full));
|
|
326
|
+
parts.push(new chapstick.Style({}, void 0, ctx).foreground(color).render(this.full));
|
|
316
327
|
}
|
|
317
328
|
return parts.join("");
|
|
318
329
|
}
|
|
319
330
|
renderSolid(filledWidth) {
|
|
320
331
|
if (filledWidth <= 0) return "";
|
|
321
|
-
const
|
|
332
|
+
const ctx = chapstick.createDefaultContext();
|
|
333
|
+
const styled = new chapstick.Style({}, void 0, ctx).foreground(this.fullColor).render(this.full);
|
|
322
334
|
return styled.repeat(filledWidth);
|
|
323
335
|
}
|
|
324
336
|
renderEmpty(emptyWidth) {
|
|
325
337
|
if (emptyWidth <= 0) return "";
|
|
326
|
-
const
|
|
338
|
+
const ctx = chapstick.createDefaultContext();
|
|
339
|
+
const styled = new chapstick.Style({}, void 0, ctx).foreground(this.emptyColor).render(this.empty);
|
|
327
340
|
return styled.repeat(emptyWidth);
|
|
328
341
|
}
|
|
329
342
|
nextFrame() {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +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":";;;;;;;;AAIO,IAAM,WAAN,MAAe;AAAA,EAGpB,WAAA,CAEkB,EAAA,EAEA,GAAA,EAEA,IAAA,EAChB;AALgB,IAAA,IAAA,CAAA,EAAA,GAAA,EAAA;AAEA,IAAA,IAAA,CAAA,GAAA,GAAA,GAAA;AAEA,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AAAA,EACf;AAAA,EATM,IAAA,GAAO,gBAAA;AAUlB;;;ACCO,IAAM,MAAA,GAAN,MAAM,OAAA,CAAO;AAAA,EACT,SAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EAET,WAAA,CAAY,MAAA,GAAuB,EAAC,EAAG;AACrC,IAAA,IAAA,CAAK,SAAA,GAAY,OAAO,SAAA,IAAa,EAAA;AACrC,IAAA,IAAA,CAAK,OAAA,GAAU,OAAO,OAAA,IAAW,CAAA;AAGjC,IAAA,IAAA,CAAK,WAAW,IAAA,CAAK,SAAA;AACrB,IAAA,IAAA,CAAK,IAAA,GAAO,OAAO,QAAA,IAAY,CAAA;AAC/B,IAAA,IAAA,CAAK,IAAA,GAAO,OAAO,QAAA,IAAY,CAAA;AAAA,EACjC;AAAA;AAAA,EAGA,QAAA,GAAmB;AACjB,IAAA,OAAO,IAAA,CAAK,IAAA;AAAA,EACd;AAAA;AAAA,EAGA,QAAA,GAAmB;AACjB,IAAA,OAAO,IAAA,CAAK,IAAA;AAAA,EACd;AAAA;AAAA,EAGA,WAAA,CAAY,WAAmB,OAAA,EAAyB;AACtD,IAAA,OAAO,IAAI,OAAA,CAAO;AAAA,MAChB,SAAA;AAAA,MACA,OAAA;AAAA,MACA,UAAU,IAAA,CAAK,IAAA;AAAA,MACf,UAAU,IAAA,CAAK;AAAA,KAChB,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAA,CAAO,QAAgB,OAAA,EAAyB;AAE9C,IAAA,MAAM,EAAA,GAAK,KAAK,GAAA,CAAI,IAAA,EAAM,KAAK,GAAA,CAAI,CAAA,EAAG,OAAA,GAAU,GAAI,CAAC,CAAA;AACrD,IAAA,MAAM,YAAA,GAAe,KAAK,IAAA,GAAO,MAAA;AAEjC,IAAA,MAAM,WAAA,GAAc,CAAC,IAAA,CAAK,QAAA,GAAW,KAAK,QAAA,GAAW,YAAA;AACrD,IAAA,MAAM,eAAe,EAAA,GAAK,IAAA,CAAK,OAAA,GAAU,IAAA,CAAK,WAAW,IAAA,CAAK,IAAA;AAC9D,IAAA,MAAM,eAAe,WAAA,GAAc,YAAA;AAEnC,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,GAAO,YAAA,GAAe,EAAA;AAC5C,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,GAAO,QAAA,GAAW,EAAA;AAExC,IAAA,OAAO,IAAI,OAAA,CAAO;AAAA,MAChB,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,QAAA;AAAA,MACA;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAA;;;ACtEA,SAAS,QAAQ,KAAA,EAAuB;AACtC,EAAA,OAAO,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,CAAC,CAAA;AACvC;AAEA,SAAS,SAAS,GAAA,EAAyB;AACzC,EAAA,MAAM,UAAA,GAAa,IAAI,UAAA,CAAW,GAAG,IAAI,GAAA,CAAI,KAAA,CAAM,CAAC,CAAA,GAAI,GAAA;AACxD,EAAA,IAAI,UAAA,CAAW,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AACpC,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,QAAA,CAAS,UAAA,EAAY,EAAE,CAAA;AAC1C,EAAA,IAAI,MAAA,CAAO,KAAA,CAAM,GAAG,CAAA,EAAG,OAAO,IAAA;AAC9B,EAAA,OAAO;AAAA,IACL,CAAA,EAAI,OAAO,EAAA,GAAM,GAAA;AAAA,IACjB,CAAA,EAAI,OAAO,CAAA,GAAK,GAAA;AAAA,IAChB,GAAG,GAAA,GAAM;AAAA,GACX;AACF;AAEA,SAAS,QAAA,CAAS,EAAE,CAAA,EAAG,CAAA,EAAG,GAAE,EAAgB;AAC1C,EAAA,MAAM,KAAA,GAAQ,CAAC,CAAA,KAAc,CAAA,CAAE,SAAS,EAAE,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAA;AAC3D,EAAA,OAAO,CAAA,CAAA,EAAI,KAAA,CAAM,CAAC,CAAC,CAAA,EAAG,KAAA,CAAM,CAAC,CAAC,CAAA,EAAG,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AAC3C;AAMO,SAAS,gBAAA,CACd,MAAA,EACA,MAAA,EACA,CAAA,EACQ;AACR,EAAA,MAAM,CAAA,GAAI,SAAS,MAAM,CAAA;AACzB,EAAA,MAAM,CAAA,GAAI,SAAS,MAAM,CAAA;AACzB,EAAA,IAAI,CAAC,CAAA,IAAK,CAAC,CAAA,EAAG,OAAO,MAAA;AAErB,EAAA,MAAM,QAAA,GAAW,QAAQ,CAAC,CAAA;AAC1B,EAAA,MAAM,GAAA,GAAM,CAAC,KAAA,EAAe,GAAA,KAC1B,KAAK,KAAA,CAAM,KAAA,GAAA,CAAS,GAAA,GAAM,KAAA,IAAS,QAAQ,CAAA;AAE7C,EAAA,OAAO,QAAA,CAAS;AAAA,IACd,CAAA,EAAG,GAAA,CAAI,CAAA,CAAE,CAAA,EAAG,EAAE,CAAC,CAAA;AAAA,IACf,CAAA,EAAG,GAAA,CAAI,CAAA,CAAE,CAAA,EAAG,EAAE,CAAC,CAAA;AAAA,IACf,CAAA,EAAG,GAAA,CAAI,CAAA,CAAE,CAAA,EAAG,EAAE,CAAC;AAAA,GAChB,CAAA;AACH;;;ACrCA,IAAM,GAAA,GAAM,EAAA;AACZ,IAAM,QAAA,GAAW,IAAA,CAAK,KAAA,CAAM,GAAA,GAAO,GAAG,CAAA;AACtC,IAAM,WAAA,GAAcA,gCAAqB,CAAE,GAAA;AAC3C,IAAM,aAAA,GAAgB,EAAA;AACtB,IAAM,YAAA,GAAe,QAAA;AACrB,IAAM,aAAA,GAAgB,QAAA;AACtB,IAAM,kBAAA,GAAqB,SAAA;AAC3B,IAAM,mBAAA,GAAsB,SAAA;AAC5B,IAAM,sBAAA,GAAyB,UAAA;AAC/B,IAAM,sBAAA,GAAyB,SAAA;AAC/B,IAAM,oBAAA,GAAuB,SAAA;AAC7B,IAAM,eAAA,GAAkB,IAAA;AACxB,IAAM,eAAA,GAAkB,IAAA;AAGxB,IAAI,MAAA,GAAS,CAAA;AACb,SAAS,MAAA,GAAiB;AACxB,EAAA,OAAO,EAAE,MAAA;AACX;AAEA,SAASC,SAAQ,KAAA,EAAuB;AACtC,EAAA,IAAI,MAAA,CAAO,KAAA,CAAM,KAAK,CAAA,EAAG,OAAO,CAAA;AAChC,EAAA,OAAO,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,CAAC,CAAA;AACvC;AAEA,SAAS,UAAA,CAAW,OAA2B,QAAA,EAA0B;AACvE,EAAA,IAAI,CAAC,OAAO,OAAO,QAAA;AAEnB,EAAA,OAAO,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA;AACzB;AAEA,SAAS,aAAA,CAAc,OAAe,GAAA,EAAqB;AACzD,EAAA,MAAM,YAAA,GAAeA,QAAAA,CAAQ,KAAK,CAAA,GAAI,GAAA;AACtC,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,sBAAsB,CAAA;AAC9C,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,CAAA,EAAG,YAAA,CAAa,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA,CAAA;AAAA,EACnC;AACA,EAAA,MAAM,SAAA,GAAY,KAAA,CAAM,CAAC,CAAA,GAAI,MAAA,CAAO,SAAS,KAAA,CAAM,CAAC,CAAA,EAAG,EAAE,CAAA,GAAI,CAAA;AAC7D,EAAA,MAAM,WAAA,GAAc,YAAA,CAAa,OAAA,CAAQ,SAAS,CAAA;AAClD,EAAA,OAAO,IAAI,OAAA,CAAQ,sBAAA,EAAwB,WAAW,CAAA,CAAE,OAAA,CAAQ,OAAO,GAAG,CAAA;AAC5E;AAEA,SAAS,MAAA,CAAO,OAAA,EAAiB,MAAA,EAAgB,QAAA,EAA2B;AAC1E,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,OAAA,GAAU,MAAM,CAAA;AACtC,EAAA,OAAO,IAAA,GAAO,eAAA,IAAmB,IAAA,CAAK,GAAA,CAAI,QAAQ,CAAA,GAAI,eAAA;AACxD;AAEA,SAAS,aAAA,CACP,OACA,QAAA,EACQ;AACR,EAAA,OAAOC,sBAAA,CAAa,KAAA,EAAO,WAAW,CAAA,IAAK,QAAA;AAC7C;AAkCA,SAAS,YAAA,GAA8B;AACrC,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,CAAA;AAAA,IACT,MAAA,EAAQ,CAAA;AAAA,IACR,QAAA,EAAU,CAAA;AAAA,IACV,IAAI,MAAA,EAAO;AAAA,IACX,GAAA,EAAK,CAAA;AAAA,IACL,MAAA,EAAQ,IAAI,MAAA,EAAO;AAAA,IACnB,aAAA,EAAe;AAAA,GACjB;AACF;AAMO,IAAM,aAAA,GAAN,MAAM,cAAA,CAAc;AAAA,EAChB,KAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA;AAAA,EACA,UAAA;AAAA,EACA,cAAA;AAAA,EACA,aAAA;AAAA,EACA,aAAA;AAAA,EACA,WAAA;AAAA,EACA,aAAA;AAAA,EACA,WAAA;AAAA,EACA,eAAA;AAAA,EAEA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,GAAA;AAAA,EACA,IAAA;AAAA,EACA,OAAA;AAAA,EACA,cAAA;AAAA,EAET,YAAY,OAAA,GAA2B,EAAC,EAAG,KAAA,GAAsB,EAAC,EAAG;AACnE,IAAA,IAAA,CAAK,KAAA,GAAQ,QAAQ,KAAA,IAAS,aAAA;AAC9B,IAAA,IAAA,CAAK,IAAA,GAAO,UAAA,CAAW,OAAA,CAAQ,IAAA,EAAM,YAAY,CAAA;AACjD,IAAA,IAAA,CAAK,KAAA,GAAQ,UAAA,CAAW,OAAA,CAAQ,KAAA,EAAO,aAAa,CAAA;AACpD,IAAA,IAAA,CAAK,SAAA,GAAY,aAAA,CAAc,OAAA,CAAQ,SAAA,EAAW,kBAAkB,CAAA;AACpE,IAAA,IAAA,CAAK,UAAA,GAAa,aAAA,CAAc,OAAA,CAAQ,UAAA,EAAY,mBAAmB,CAAA;AACvE,IAAA,IAAA,CAAK,cAAA,GAAiB,QAAQ,cAAA,IAAkB,IAAA;AAChD,IAAA,IAAA,CAAK,aAAA,GAAgB,QAAQ,aAAA,IAAiB,sBAAA;AAC9C,IAAA,IAAA,CAAK,eAAA,GAAkB,OAAA,CAAQ,eAAA,IAAmB,IAAIC,eAAA,EAAM;AAE5D,IAAA,MAAM,QAAQ,OAAA,CAAQ,aAAA,GAClBD,uBAAa,OAAA,CAAQ,aAAA,EAAe,WAAW,CAAA,GAC/C,MAAA;AACJ,IAAA,MAAM,MAAM,OAAA,CAAQ,WAAA,GAChBA,uBAAa,OAAA,CAAQ,WAAA,EAAa,WAAW,CAAA,GAC7C,MAAA;AACJ,IAAA,IAAA,CAAK,aAAA,GAAgB,KAAA;AACrB,IAAA,IAAA,CAAK,WAAA,GAAc,GAAA;AACnB,IAAA,IAAA,CAAK,aAAA,GAAgB,QAAQ,aAAA,IAAiB,KAAA;AAC9C,IAAA,IAAA,CAAK,WAAA,GAAc,OAAA,CAAQ,KAAA,IAAS,GAAG,CAAA;AAEvC,IAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,eAAA,IAAmB,KAAA,CAAM,QAAQ,SAAA,IAAa,EAAA;AACxE,IAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,aAAA,IAAiB,KAAA,CAAM,QAAQ,OAAA,IAAW,CAAA;AAClE,IAAA,MAAM,YAAY,EAAE,GAAG,YAAA,EAAa,EAAG,GAAG,KAAA,EAAM;AAChD,IAAA,MAAM,MAAA,GAAS,MAAM,MAAA,IAAU,IAAI,OAAO,EAAE,SAAA,EAAW,SAAS,CAAA;AAChE,IAAA,IAAA,CAAK,OAAA,GAAU,MAAA,CAAO,WAAA,CAAY,SAAA,EAAW,OAAO,CAAA;AAEpD,IAAA,IAAA,CAAK,QAAA,GAAWD,QAAAA,CAAQ,SAAA,CAAU,OAAO,CAAA;AACzC,IAAA,IAAA,CAAK,OAAA,GAAUA,QAAAA,CAAQ,SAAA,CAAU,MAAM,CAAA;AACvC,IAAA,IAAA,CAAK,YAAY,SAAA,CAAU,QAAA;AAC3B,IAAA,IAAA,CAAK,MAAM,SAAA,CAAU,EAAA;AACrB,IAAA,IAAA,CAAK,OAAO,SAAA,CAAU,GAAA;AACtB,IAAA,IAAA,CAAK,iBAAiB,SAAA,CAAU,aAAA;AAAA,EAClC;AAAA;AAAA,EAGA,OAAO,GAAA,CAAI,OAAA,GAA2B,EAAC,EAAkB;AACvD,IAAA,OAAO,IAAI,eAAc,OAAO,CAAA;AAAA,EAClC;AAAA;AAAA,EAGA,OAAO,mBAAA,CAAoB,OAAA,GAA2B,EAAC,EAAkB;AACvE,IAAA,OAAO,IAAI,cAAA,CAAc;AAAA,MACvB,GAAG,OAAA;AAAA,MACH,aAAA,EAAe,QAAQ,aAAA,IAAiB,sBAAA;AAAA,MACxC,WAAA,EAAa,QAAQ,WAAA,IAAe;AAAA,KACrC,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,OAAO,YAAA,CACL,MAAA,EACA,MAAA,EACA,OAAA,GAA2B,EAAC,EACb;AACf,IAAA,OAAO,IAAI,cAAA,CAAc;AAAA,MACvB,GAAG,OAAA;AAAA,MACH,aAAA,EAAe,MAAA;AAAA,MACf,WAAA,EAAa;AAAA,KACd,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,OAAO,aAAA,CACL,KAAA,EACA,OAAA,GAA2B,EAAC,EACb;AACf,IAAA,OAAO,IAAI,cAAA,CAAc;AAAA,MACvB,GAAG,OAAA;AAAA,MACH,SAAA,EAAW,KAAA;AAAA,MACX,aAAA,EAAe,MAAA;AAAA,MACf,WAAA,EAAa;AAAA,KACd,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,EAAA,GAAa;AACX,IAAA,OAAO,IAAA,CAAK,GAAA;AAAA,EACd;AAAA;AAAA,EAGA,OAAA,GAAkB;AAChB,IAAA,OAAOA,QAAAA,CAAQ,KAAK,QAAQ,CAAA;AAAA,EAC9B;AAAA;AAAA,EAGA,aAAA,GAAwB;AACtB,IAAA,OAAOA,QAAAA,CAAQ,KAAK,OAAO,CAAA;AAAA,EAC7B;AAAA;AAAA,EAGA,IAAA,GAAiB;AACf,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA,EAGA,OAAO,GAAA,EAAqC;AAC1C,IAAA,IAAI,EAAE,eAAe,QAAA,CAAA,EAAW;AAC9B,MAAA,OAAO,CAAC,MAAM,IAAI,CAAA;AAAA,IACpB;AACA,IAAA,IAAI,IAAI,EAAA,KAAO,IAAA,CAAK,OAAO,GAAA,CAAI,GAAA,KAAQ,KAAK,IAAA,EAAM;AAChD,MAAA,OAAO,CAAC,MAAM,IAAI,CAAA;AAAA,IACpB;AAEA,IAAA,MAAM,EAAA,GACJ,IAAA,CAAK,cAAA,KAAmB,IAAA,GACpB,WACA,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,GAAA,CAAI,KAAK,OAAA,EAAQ,GAAI,IAAA,CAAK,cAAA,CAAe,SAAS,CAAA;AAEpE,IAAA,MAAM,SAAS,IAAA,CAAK,OAAA,CAAQ,MAAA,CAAO,IAAA,CAAK,SAAS,EAAE,CAAA;AACnD,IAAA,MAAM,WAAA,GAAcA,QAAAA,CAAQ,MAAA,CAAO,QAAA,EAAU,CAAA;AAC7C,IAAA,MAAM,YAAA,GAAe,OAAO,QAAA,EAAS;AAErC,IAAA,MAAM,IAAA,GAAO,KAAK,SAAA,CAAU;AAAA,MAC1B,OAAA,EAAS,WAAA;AAAA,MACT,QAAA,EAAU,YAAA;AAAA,MACV,KAAK,IAAA,CAAK,IAAA;AAAA,MACV,MAAA;AAAA,MACA,eAAe,GAAA,CAAI;AAAA,KACpB,CAAA;AAED,IAAA,IAAI,MAAA,CAAO,WAAA,EAAa,IAAA,CAAK,OAAA,EAAS,YAAY,CAAA,EAAG;AACnD,MAAA,OAAO,CAAC,MAAM,IAAI,CAAA;AAAA,IACpB;AAEA,IAAA,OAAO,CAAC,IAAA,EAAM,IAAA,CAAK,SAAA,EAAW,CAAA;AAAA,EAChC;AAAA;AAAA,EAGA,WAAW,OAAA,EAA4C;AACrD,IAAA,MAAM,OAAA,GAAUA,SAAQ,OAAO,CAAA;AAC/B,IAAA,MAAM,IAAA,GAAO,KAAK,SAAA,CAAU;AAAA,MAC1B,MAAA,EAAQ,OAAA;AAAA,MACR,GAAA,EAAK,KAAK,IAAA,GAAO,CAAA;AAAA,MACjB,aAAA,EAAe;AAAA,KAChB,CAAA;AACD,IAAA,OAAO,CAAC,IAAA,EAAM,IAAA,CAAK,SAAA,EAAW,CAAA;AAAA,EAChC;AAAA;AAAA,EAGA,YAAY,KAAA,EAA0C;AACpD,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,IAAA,CAAK,OAAA,GAAU,KAAK,CAAA;AAAA,EAC7C;AAAA;AAAA,EAGA,gBAAA,CAAiB,WAAmB,OAAA,EAAgC;AAClE,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,OAAA,CAAQ,WAAA,CAAY,WAAW,OAAO,CAAA;AAC1D,IAAA,OAAO,IAAA,CAAK,SAAA,CAAU,EAAE,MAAA,EAAQ,CAAA;AAAA,EAClC;AAAA;AAAA,EAGA,IAAA,GAAe;AACb,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,OAAA,EAAS,CAAA;AAAA,EACnC;AAAA;AAAA,EAGA,OAAO,OAAA,EAAyB;AAC9B,IAAA,MAAM,GAAA,GAAMA,SAAQ,OAAO,CAAA;AAC3B,IAAA,MAAM,WAAA,GAAc,IAAA,CAAK,cAAA,GACrB,IAAA,CAAK,eAAA,CAAgB,MAAA,CAAO,aAAA,CAAc,GAAA,EAAK,IAAA,CAAK,aAAa,CAAC,CAAA,GAClE,EAAA;AAEJ,IAAA,MAAM,YAAA,GAAe,IAAA,CAAK,cAAA,GAAiBG,eAAA,CAAU,WAAW,CAAA,GAAI,CAAA;AACpE,IAAA,MAAM,gBAAgB,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,QAAQ,YAAY,CAAA;AAC3D,IAAA,MAAM,WAAA,GAAc,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,KAAA,CAAM,aAAA,GAAgB,GAAG,CAAC,CAAA;AAC/D,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,gBAAgB,WAAW,CAAA;AAE1D,IAAA,MAAM,MAAM,CAAA,EAAG,IAAA,CAAK,WAAA,GAAc,IAAA,CAAK,eAAe,WAAA,EAAa,aAAa,CAAA,GAAI,IAAA,CAAK,YAAY,WAAW,CAAC,GAAG,IAAA,CAAK,WAAA,CAAY,UAAU,CAAC,CAAA,CAAA;AAEhJ,IAAA,OAAO,CAAA,EAAG,GAAG,CAAA,EAAG,WAAW,CAAA,CAAA;AAAA,EAC7B;AAAA,EAEQ,cAAA,CAAe,aAAqB,UAAA,EAA4B;AACtE,IAAA,IACE,CAAC,IAAA,CAAK,WAAA,IACN,WAAA,IAAe,CAAA,IACf,CAAC,IAAA,CAAK,aAAA,IACN,CAAC,IAAA,CAAK,WAAA,EACN;AACA,MAAA,OAAO,EAAA;AAAA,IACT;AAEA,IAAA,MAAM,QAAkB,EAAC;AACzB,IAAA,MAAM,WAAA,GAAc,IAAA,CAAK,aAAA,GACrB,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,WAAA,GAAc,CAAC,CAAA,GAC3B,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,aAAa,CAAC,CAAA;AAE9B,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,WAAA,EAAa,CAAA,EAAA,EAAK;AACpC,MAAA,MAAM,CAAA,GAAI,WAAA,KAAgB,CAAA,GAAI,GAAA,GAAM,CAAA,GAAI,WAAA;AACxC,MAAA,MAAM,QAAQ,gBAAA,CAAiB,IAAA,CAAK,aAAA,EAAe,IAAA,CAAK,aAAa,CAAC,CAAA;AACtE,MAAA,KAAA,CAAM,IAAA,CAAK,IAAID,eAAA,EAAM,CAAE,UAAA,CAAW,KAAK,CAAA,CAAE,MAAA,CAAO,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,IAC5D;AAEA,IAAA,OAAO,KAAA,CAAM,KAAK,EAAE,CAAA;AAAA,EACtB;AAAA,EAEQ,YAAY,WAAA,EAA6B;AAC/C,IAAA,IAAI,WAAA,IAAe,GAAG,OAAO,EAAA;AAC7B,IAAA,MAAM,MAAA,GAAS,IAAIA,eAAA,EAAM,CAAE,UAAA,CAAW,KAAK,SAAS,CAAA,CAAE,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA;AACtE,IAAA,OAAO,MAAA,CAAO,OAAO,WAAW,CAAA;AAAA,EAClC;AAAA,EAEQ,YAAY,UAAA,EAA4B;AAC9C,IAAA,IAAI,UAAA,IAAc,GAAG,OAAO,EAAA;AAC5B,IAAA,MAAM,MAAA,GAAS,IAAIA,eAAA,EAAM,CAAE,UAAA,CAAW,KAAK,UAAU,CAAA,CAAE,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA;AACxE,IAAA,OAAO,MAAA,CAAO,OAAO,UAAU,CAAA;AAAA,EACjC;AAAA,EAEQ,SAAA,GAA2B;AACjC,IAAA,MAAM,KAAK,IAAA,CAAK,GAAA;AAChB,IAAA,MAAM,MAAM,IAAA,CAAK,IAAA;AACjB,IAAA,OAAOE,QAAA,CAAK,UAAU,CAAC,IAAA,KAAS,IAAI,QAAA,CAAS,EAAA,EAAI,GAAA,EAAK,IAAI,CAAC,CAAA;AAAA,EAC7D;AAAA,EAEQ,UAAU,KAAA,EAAoC;AACpD,IAAA,OAAO,IAAI,cAAA;AAAA,MACT;AAAA,QACE,OAAO,IAAA,CAAK,KAAA;AAAA,QACZ,MAAM,IAAA,CAAK,IAAA;AAAA,QACX,OAAO,IAAA,CAAK,KAAA;AAAA,QACZ,WAAW,IAAA,CAAK,SAAA;AAAA,QAChB,YAAY,IAAA,CAAK,UAAA;AAAA,QACjB,gBAAgB,IAAA,CAAK,cAAA;AAAA,QACrB,eAAe,IAAA,CAAK,aAAA;AAAA,QACpB,eAAe,IAAA,CAAK,aAAA;AAAA,QACpB,aAAa,IAAA,CAAK,WAAA;AAAA,QAClB,eAAe,IAAA,CAAK,aAAA;AAAA,QACpB,eAAA,EAAiB,KAAK,OAAA,CAAQ,SAAA;AAAA,QAC9B,aAAA,EAAe,KAAK,OAAA,CAAQ,OAAA;AAAA,QAC5B,iBAAiB,IAAA,CAAK;AAAA,OACxB;AAAA,MACA;AAAA,QACE,SAAS,IAAA,CAAK,QAAA;AAAA,QACd,QAAQ,IAAA,CAAK,OAAA;AAAA,QACb,UAAU,IAAA,CAAK,SAAA;AAAA,QACf,IAAI,IAAA,CAAK,GAAA;AAAA,QACT,KAAK,IAAA,CAAK,IAAA;AAAA,QACV,QAAQ,IAAA,CAAK,OAAA;AAAA,QACb,eAAe,IAAA,CAAK,cAAA;AAAA,QACpB,GAAG;AAAA;AACL,KACF;AAAA,EACF;AACF","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"]}
|
|
1
|
+
{"version":3,"sources":["../src/messages.ts","../src/spring.ts","../src/gradient.ts","../src/model.ts"],"names":["clamp01","createDefaultContext","resolveColor","Style","next","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 * @internal\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 * @internal\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 type StyleContext,\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_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\n/**\n * Lazily get the current default environment.\n * This is called at render time to respect any context set via setDefaultContext().\n */\nfunction getDefaultEnv() {\n return createDefaultContext().env\n}\n\nfunction resolvedColor(\n color: ColorInput | undefined,\n fallback: string,\n): string {\n return resolveColor(color, getDefaultEnv()) ?? 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 * Style context for rendering colors.\n *\n * @remarks\n * In browser environments, pass the browser style context to enable colors.\n * If not provided, uses the default context from `createDefaultContext()`.\n */\n styleContext?: StyleContext\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, getDefaultEnv())\n : undefined\n const end = options.gradientEnd\n ? resolveColor(options.gradientEnd, getDefaultEnv())\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\n // If already animating, just update target without resetting animation\n if (this.isAnimating()) {\n const next = this.withState({ target: clamped })\n return [next, null]\n }\n\n // Not animating - start new animation\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 /** Returns true if animation is in progress (not yet settled). */\n private isAnimating(): boolean {\n return !settle(this.#percent, this.#target, this.#velocity)\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 const ctx = createDefaultContext()\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({}, undefined, ctx).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 ctx = createDefaultContext()\n const styled = new Style({}, undefined, ctx).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 ctx = createDefaultContext()\n const styled = new Style({}, undefined, ctx).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
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Cmd, Msg } from '@boba-cli/tea';
|
|
2
|
-
import { Style, ColorInput } from '@boba-cli/chapstick';
|
|
2
|
+
import { Style, ColorInput, StyleContext } from '@boba-cli/chapstick';
|
|
3
3
|
|
|
4
4
|
interface SpringConfig {
|
|
5
5
|
/** Oscillation speed (Hz). */
|
|
@@ -12,6 +12,7 @@ interface SpringConfig {
|
|
|
12
12
|
velocity?: number;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
+
* @internal
|
|
15
16
|
* Minimal damped spring integrator (ported from harmonica).
|
|
16
17
|
* Stores its own position/velocity and integrates using a simple
|
|
17
18
|
* damped harmonic oscillator step.
|
|
@@ -52,6 +53,14 @@ interface ProgressOptions {
|
|
|
52
53
|
springFrequency?: number;
|
|
53
54
|
springDamping?: number;
|
|
54
55
|
percentageStyle?: Style;
|
|
56
|
+
/**
|
|
57
|
+
* Style context for rendering colors.
|
|
58
|
+
*
|
|
59
|
+
* @remarks
|
|
60
|
+
* In browser environments, pass the browser style context to enable colors.
|
|
61
|
+
* If not provided, uses the default context from `createDefaultContext()`.
|
|
62
|
+
*/
|
|
63
|
+
styleContext?: StyleContext;
|
|
55
64
|
}
|
|
56
65
|
interface ProgressState {
|
|
57
66
|
percent: number;
|
|
@@ -102,6 +111,8 @@ declare class ProgressModel {
|
|
|
102
111
|
update(msg: Msg): [ProgressModel, Cmd<Msg>];
|
|
103
112
|
/** Set a new target percent and start animation. */
|
|
104
113
|
setPercent(percent: number): [ProgressModel, Cmd<Msg>];
|
|
114
|
+
/** Returns true if animation is in progress (not yet settled). */
|
|
115
|
+
private isAnimating;
|
|
105
116
|
/** Increment the target percent. */
|
|
106
117
|
incrPercent(delta: number): [ProgressModel, Cmd<Msg>];
|
|
107
118
|
/** Update the spring configuration (keeps current state). */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Cmd, Msg } from '@boba-cli/tea';
|
|
2
|
-
import { Style, ColorInput } from '@boba-cli/chapstick';
|
|
2
|
+
import { Style, ColorInput, StyleContext } from '@boba-cli/chapstick';
|
|
3
3
|
|
|
4
4
|
interface SpringConfig {
|
|
5
5
|
/** Oscillation speed (Hz). */
|
|
@@ -12,6 +12,7 @@ interface SpringConfig {
|
|
|
12
12
|
velocity?: number;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
+
* @internal
|
|
15
16
|
* Minimal damped spring integrator (ported from harmonica).
|
|
16
17
|
* Stores its own position/velocity and integrates using a simple
|
|
17
18
|
* damped harmonic oscillator step.
|
|
@@ -52,6 +53,14 @@ interface ProgressOptions {
|
|
|
52
53
|
springFrequency?: number;
|
|
53
54
|
springDamping?: number;
|
|
54
55
|
percentageStyle?: Style;
|
|
56
|
+
/**
|
|
57
|
+
* Style context for rendering colors.
|
|
58
|
+
*
|
|
59
|
+
* @remarks
|
|
60
|
+
* In browser environments, pass the browser style context to enable colors.
|
|
61
|
+
* If not provided, uses the default context from `createDefaultContext()`.
|
|
62
|
+
*/
|
|
63
|
+
styleContext?: StyleContext;
|
|
55
64
|
}
|
|
56
65
|
interface ProgressState {
|
|
57
66
|
percent: number;
|
|
@@ -102,6 +111,8 @@ declare class ProgressModel {
|
|
|
102
111
|
update(msg: Msg): [ProgressModel, Cmd<Msg>];
|
|
103
112
|
/** Set a new target percent and start animation. */
|
|
104
113
|
setPercent(percent: number): [ProgressModel, Cmd<Msg>];
|
|
114
|
+
/** Returns true if animation is in progress (not yet settled). */
|
|
115
|
+
private isAnimating;
|
|
105
116
|
/** Increment the target percent. */
|
|
106
117
|
incrPercent(delta: number): [ProgressModel, Cmd<Msg>];
|
|
107
118
|
/** Update the spring configuration (keeps current state). */
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { tick } from '@boba-cli/tea';
|
|
2
|
-
import {
|
|
2
|
+
import { Style, resolveColor, width, createDefaultContext } from '@boba-cli/chapstick';
|
|
3
3
|
|
|
4
4
|
// src/model.ts
|
|
5
5
|
|
|
@@ -100,7 +100,6 @@ function interpolateColor(colorA, colorB, t) {
|
|
|
100
100
|
// src/model.ts
|
|
101
101
|
var FPS = 60;
|
|
102
102
|
var FRAME_MS = Math.round(1e3 / FPS);
|
|
103
|
-
var DEFAULT_ENV = createDefaultContext().env;
|
|
104
103
|
var DEFAULT_WIDTH = 40;
|
|
105
104
|
var DEFAULT_FULL = "\u2588";
|
|
106
105
|
var DEFAULT_EMPTY = "\u2591";
|
|
@@ -137,8 +136,11 @@ function settle(percent, target, velocity) {
|
|
|
137
136
|
const dist = Math.abs(percent - target);
|
|
138
137
|
return dist < SETTLE_DISTANCE && Math.abs(velocity) < SETTLE_VELOCITY;
|
|
139
138
|
}
|
|
139
|
+
function getDefaultEnv() {
|
|
140
|
+
return createDefaultContext().env;
|
|
141
|
+
}
|
|
140
142
|
function resolvedColor(color, fallback) {
|
|
141
|
-
return resolveColor(color,
|
|
143
|
+
return resolveColor(color, getDefaultEnv()) ?? fallback;
|
|
142
144
|
}
|
|
143
145
|
function defaultState() {
|
|
144
146
|
return {
|
|
@@ -180,8 +182,8 @@ var ProgressModel = class _ProgressModel {
|
|
|
180
182
|
this.showPercentage = options.showPercentage ?? true;
|
|
181
183
|
this.percentFormat = options.percentFormat ?? DEFAULT_PERCENT_FORMAT;
|
|
182
184
|
this.percentageStyle = options.percentageStyle ?? new Style();
|
|
183
|
-
const start = options.gradientStart ? resolveColor(options.gradientStart,
|
|
184
|
-
const end = options.gradientEnd ? resolveColor(options.gradientEnd,
|
|
185
|
+
const start = options.gradientStart ? resolveColor(options.gradientStart, getDefaultEnv()) : void 0;
|
|
186
|
+
const end = options.gradientEnd ? resolveColor(options.gradientEnd, getDefaultEnv()) : void 0;
|
|
185
187
|
this.gradientStart = start;
|
|
186
188
|
this.gradientEnd = end;
|
|
187
189
|
this.scaleGradient = options.scaleGradient ?? false;
|
|
@@ -270,6 +272,10 @@ var ProgressModel = class _ProgressModel {
|
|
|
270
272
|
/** Set a new target percent and start animation. */
|
|
271
273
|
setPercent(percent) {
|
|
272
274
|
const clamped = clamp012(percent);
|
|
275
|
+
if (this.isAnimating()) {
|
|
276
|
+
const next2 = this.withState({ target: clamped });
|
|
277
|
+
return [next2, null];
|
|
278
|
+
}
|
|
273
279
|
const next = this.withState({
|
|
274
280
|
target: clamped,
|
|
275
281
|
tag: this.#tag + 1,
|
|
@@ -277,6 +283,10 @@ var ProgressModel = class _ProgressModel {
|
|
|
277
283
|
});
|
|
278
284
|
return [next, next.nextFrame()];
|
|
279
285
|
}
|
|
286
|
+
/** Returns true if animation is in progress (not yet settled). */
|
|
287
|
+
isAnimating() {
|
|
288
|
+
return !settle(this.#percent, this.#target, this.#velocity);
|
|
289
|
+
}
|
|
280
290
|
/** Increment the target percent. */
|
|
281
291
|
incrPercent(delta) {
|
|
282
292
|
return this.setPercent(this.#target + delta);
|
|
@@ -307,21 +317,24 @@ var ProgressModel = class _ProgressModel {
|
|
|
307
317
|
}
|
|
308
318
|
const parts = [];
|
|
309
319
|
const denominator = this.scaleGradient ? Math.max(1, filledWidth - 1) : Math.max(1, totalWidth - 1);
|
|
320
|
+
const ctx = createDefaultContext();
|
|
310
321
|
for (let i = 0; i < filledWidth; i++) {
|
|
311
322
|
const t = filledWidth === 1 ? 0.5 : i / denominator;
|
|
312
323
|
const color = interpolateColor(this.gradientStart, this.gradientEnd, t);
|
|
313
|
-
parts.push(new Style().foreground(color).render(this.full));
|
|
324
|
+
parts.push(new Style({}, void 0, ctx).foreground(color).render(this.full));
|
|
314
325
|
}
|
|
315
326
|
return parts.join("");
|
|
316
327
|
}
|
|
317
328
|
renderSolid(filledWidth) {
|
|
318
329
|
if (filledWidth <= 0) return "";
|
|
319
|
-
const
|
|
330
|
+
const ctx = createDefaultContext();
|
|
331
|
+
const styled = new Style({}, void 0, ctx).foreground(this.fullColor).render(this.full);
|
|
320
332
|
return styled.repeat(filledWidth);
|
|
321
333
|
}
|
|
322
334
|
renderEmpty(emptyWidth) {
|
|
323
335
|
if (emptyWidth <= 0) return "";
|
|
324
|
-
const
|
|
336
|
+
const ctx = createDefaultContext();
|
|
337
|
+
const styled = new Style({}, void 0, ctx).foreground(this.emptyColor).render(this.empty);
|
|
325
338
|
return styled.repeat(emptyWidth);
|
|
326
339
|
}
|
|
327
340
|
nextFrame() {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"sources":["../src/messages.ts","../src/spring.ts","../src/gradient.ts","../src/model.ts"],"names":["clamp01","next","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 * @internal\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 * @internal\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 type StyleContext,\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_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\n/**\n * Lazily get the current default environment.\n * This is called at render time to respect any context set via setDefaultContext().\n */\nfunction getDefaultEnv() {\n return createDefaultContext().env\n}\n\nfunction resolvedColor(\n color: ColorInput | undefined,\n fallback: string,\n): string {\n return resolveColor(color, getDefaultEnv()) ?? 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 * Style context for rendering colors.\n *\n * @remarks\n * In browser environments, pass the browser style context to enable colors.\n * If not provided, uses the default context from `createDefaultContext()`.\n */\n styleContext?: StyleContext\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, getDefaultEnv())\n : undefined\n const end = options.gradientEnd\n ? resolveColor(options.gradientEnd, getDefaultEnv())\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\n // If already animating, just update target without resetting animation\n if (this.isAnimating()) {\n const next = this.withState({ target: clamped })\n return [next, null]\n }\n\n // Not animating - start new animation\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 /** Returns true if animation is in progress (not yet settled). */\n private isAnimating(): boolean {\n return !settle(this.#percent, this.#target, this.#velocity)\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 const ctx = createDefaultContext()\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({}, undefined, ctx).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 ctx = createDefaultContext()\n const styled = new Style({}, undefined, ctx).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 ctx = createDefaultContext()\n const styled = new Style({}, undefined, ctx).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
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boba-cli/progress",
|
|
3
3
|
"description": "Animated progress bar for Boba terminal UIs",
|
|
4
|
-
"version": "0.1.0-alpha.
|
|
4
|
+
"version": "0.1.0-alpha.4",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@boba-cli/chapstick": "0.1.0-alpha.
|
|
7
|
-
"@boba-cli/tea": "
|
|
6
|
+
"@boba-cli/chapstick": "0.1.0-alpha.3",
|
|
7
|
+
"@boba-cli/tea": "1.0.0-alpha.2"
|
|
8
8
|
},
|
|
9
9
|
"devDependencies": {
|
|
10
10
|
"typescript": "5.8.2",
|