ink-hud 0.1.0

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/dist/index.cjs ADDED
@@ -0,0 +1,3012 @@
1
+ 'use strict';
2
+
3
+ var React6 = require('react');
4
+ var chalk = require('chalk');
5
+ var tinygradient = require('tinygradient');
6
+ var ink = require('ink');
7
+
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var React6__default = /*#__PURE__*/_interopDefault(React6);
11
+ var chalk__default = /*#__PURE__*/_interopDefault(chalk);
12
+ var tinygradient__default = /*#__PURE__*/_interopDefault(tinygradient);
13
+
14
+ // src/core/renderer.ts
15
+ var Renderer = class {
16
+ // ============================================================
17
+ // Common implementations shared by all renderers
18
+ // ============================================================
19
+ /**
20
+ * Create a blank Canvas
21
+ * @param width - Canvas width (pixels)
22
+ * @param height - Canvas height (pixels)
23
+ * @returns 2D pixel array
24
+ */
25
+ createCanvas(width, height) {
26
+ return Array.from(
27
+ { length: height },
28
+ () => Array.from({ length: width }, () => ({ active: false }))
29
+ );
30
+ }
31
+ /**
32
+ * Set a single pixel on the Canvas
33
+ * @param canvas - Canvas
34
+ * @param x - x coordinate
35
+ * @param y - y coordinate
36
+ * @param pixel - Pixel properties (active, color, etc.)
37
+ */
38
+ setPixel(canvas, x, y, pixel = { active: true }) {
39
+ const row = canvas[y];
40
+ if (y >= 0 && y < canvas.length && row && x >= 0 && x < row.length) {
41
+ const current = row[x];
42
+ if (current) {
43
+ Object.assign(current, pixel);
44
+ }
45
+ }
46
+ }
47
+ /**
48
+ * Draw line segments (Bresenham's algorithm)
49
+ */
50
+ drawLine(canvas, x0, y0, x1, y1, pixel = { active: true }) {
51
+ const dx = Math.abs(x1 - x0);
52
+ const dy = Math.abs(y1 - y0);
53
+ const sx = x0 < x1 ? 1 : -1;
54
+ const sy = y0 < y1 ? 1 : -1;
55
+ let err = dx - dy;
56
+ let x = Math.round(x0);
57
+ let y = Math.round(y0);
58
+ const endX = Math.round(x1);
59
+ const endY = Math.round(y1);
60
+ while (true) {
61
+ this.setPixel(canvas, x, y, pixel);
62
+ if (x === endX && y === endY) break;
63
+ const e2 = 2 * err;
64
+ if (e2 > -dy) {
65
+ err -= dy;
66
+ x += sx;
67
+ }
68
+ if (e2 < dx) {
69
+ err += dx;
70
+ y += sy;
71
+ }
72
+ }
73
+ }
74
+ /**
75
+ * Draw circle or ring (Midpoint Circle algorithm)
76
+ */
77
+ drawCircle(canvas, centerX, centerY, radius, filled = false, pixel = { active: true }) {
78
+ if (filled) {
79
+ for (let y = -radius; y <= radius; y++) {
80
+ const width = Math.sqrt(radius * radius - y * y);
81
+ for (let x = -width; x <= width; x++) {
82
+ this.setPixel(canvas, Math.round(centerX + x), Math.round(centerY + y), pixel);
83
+ }
84
+ }
85
+ } else {
86
+ let x = 0;
87
+ let y = radius;
88
+ let d = 1 - radius;
89
+ while (x <= y) {
90
+ this.setPixel(canvas, centerX + x, centerY + y, pixel);
91
+ this.setPixel(canvas, centerX - x, centerY + y, pixel);
92
+ this.setPixel(canvas, centerX + x, centerY - y, pixel);
93
+ this.setPixel(canvas, centerX - x, centerY - y, pixel);
94
+ this.setPixel(canvas, centerX + y, centerY + x, pixel);
95
+ this.setPixel(canvas, centerX - y, centerY + x, pixel);
96
+ this.setPixel(canvas, centerX + y, centerY - x, pixel);
97
+ this.setPixel(canvas, centerX - y, centerY - x, pixel);
98
+ x++;
99
+ if (d < 0) {
100
+ d += 2 * x + 1;
101
+ } else {
102
+ y--;
103
+ d += 2 * (x - y) + 1;
104
+ }
105
+ }
106
+ }
107
+ }
108
+ /**
109
+ * Draw arc (Parametric equation)
110
+ */
111
+ drawArc(canvas, centerX, centerY, radius, startAngle, endAngle, thickness = 1, pixel = { active: true }) {
112
+ let start = startAngle;
113
+ let end = endAngle;
114
+ if (start > end) {
115
+ [start, end] = [end, start];
116
+ }
117
+ const angleStep = 1 / (radius * 2);
118
+ for (let r = Math.max(0, radius - thickness + 1); r <= radius; r++) {
119
+ for (let theta = start; theta <= end; theta += angleStep) {
120
+ const x = centerX + r * Math.cos(theta);
121
+ const y = centerY + r * Math.sin(theta);
122
+ this.setPixel(canvas, Math.round(x), Math.round(y), pixel);
123
+ }
124
+ }
125
+ }
126
+ /**
127
+ * Draw rectangle
128
+ */
129
+ drawRect(canvas, x, y, width, height, filled = false, pixel = { active: true }) {
130
+ if (filled) {
131
+ for (let py = y; py < y + height; py++) {
132
+ for (let px = x; px < x + width; px++) {
133
+ this.setPixel(canvas, px, py, pixel);
134
+ }
135
+ }
136
+ } else {
137
+ for (let px = x; px < x + width; px++) {
138
+ this.setPixel(canvas, px, y + height - 1, pixel);
139
+ }
140
+ for (let py = y; py < y + height; py++) {
141
+ this.setPixel(canvas, x, py, pixel);
142
+ }
143
+ for (let py = y; py < y + height; py++) {
144
+ this.setPixel(canvas, x + width - 1, py, pixel);
145
+ }
146
+ }
147
+ }
148
+ // ============================================================
149
+ // Helper methods
150
+ // ============================================================
151
+ /**
152
+ * Get renderer resolution
153
+ */
154
+ getResolution() {
155
+ return this.getMetadata().resolution;
156
+ }
157
+ /**
158
+ * Get renderer name
159
+ */
160
+ getName() {
161
+ return this.getMetadata().name;
162
+ }
163
+ /**
164
+ * Calculate number of character rows and columns needed to render specified dimensions
165
+ */
166
+ calculateCharDimensions(pixelWidth, pixelHeight) {
167
+ const res = this.getResolution();
168
+ return {
169
+ cols: Math.ceil(pixelWidth / res.horizontal),
170
+ rows: Math.ceil(pixelHeight / res.vertical)
171
+ };
172
+ }
173
+ };
174
+
175
+ // src/core/braille.ts
176
+ var BRAILLE_BASE = 10240;
177
+ var DOT_WEIGHTS = [1, 2, 4, 8, 16, 32, 64, 128];
178
+ var BrailleRenderer = class extends Renderer {
179
+ getMetadata() {
180
+ return {
181
+ name: "braille",
182
+ displayName: "Braille",
183
+ description: "Braille character (\u28FF) 2x4 matrix, 8x resolution",
184
+ resolution: {
185
+ horizontal: 2,
186
+ vertical: 4
187
+ },
188
+ requiresUtf8: true,
189
+ requiresUnicode: true,
190
+ minScore: 80
191
+ };
192
+ }
193
+ // ============================================================
194
+ // Braille-specific private methods
195
+ // ============================================================
196
+ createBrailleChar(dots) {
197
+ let code = BRAILLE_BASE;
198
+ for (let i = 0; i < 8 && i < DOT_WEIGHTS.length; i++) {
199
+ if (dots[i]) {
200
+ const weight = DOT_WEIGHTS[i];
201
+ if (weight !== void 0) {
202
+ code += weight;
203
+ }
204
+ }
205
+ }
206
+ return String.fromCharCode(code);
207
+ }
208
+ pixelToDotIndex(x, y) {
209
+ if (x === 0) {
210
+ return y === 3 ? 6 : y;
211
+ }
212
+ return y === 3 ? 7 : y + 3;
213
+ }
214
+ fillBrailleDots(pixels, cx, cy, width, height) {
215
+ const dots = Array(8).fill(false);
216
+ for (let py = 0; py < 4; py++) {
217
+ for (let px = 0; px < 2; px++) {
218
+ const pixelY = cy * 4 + py;
219
+ const pixelX = cx * 2 + px;
220
+ if (pixelY < height && pixelX < width) {
221
+ dots[this.pixelToDotIndex(px, py)] = pixels[pixelY]?.[pixelX]?.active ?? false;
222
+ }
223
+ }
224
+ }
225
+ return dots;
226
+ }
227
+ resolveColor(pixels, cx, cy, width, height) {
228
+ const counts = /* @__PURE__ */ new Map();
229
+ for (let py = 0; py < 4; py++) {
230
+ for (let px = 0; px < 2; px++) {
231
+ const pixelY = cy * 4 + py;
232
+ const pixelX = cx * 2 + px;
233
+ if (pixelY < height && pixelX < width) {
234
+ const pixel = pixels[pixelY]?.[pixelX];
235
+ if (pixel?.active && pixel.color) {
236
+ counts.set(pixel.color, (counts.get(pixel.color) ?? 0) + 1);
237
+ }
238
+ }
239
+ }
240
+ }
241
+ if (counts.size === 0) return void 0;
242
+ let maxCount = 0;
243
+ let dominantColor;
244
+ for (const [color, count] of counts) {
245
+ if (count > maxCount) {
246
+ maxCount = count;
247
+ dominantColor = color;
248
+ }
249
+ }
250
+ return dominantColor;
251
+ }
252
+ // ============================================================
253
+ // Methods that subclasses must implement
254
+ // ============================================================
255
+ renderCanvas(pixels, width, height) {
256
+ const charWidth = Math.ceil(width / 2);
257
+ const charHeight = Math.ceil(height / 4);
258
+ const lines = [];
259
+ for (let cy = 0; cy < charHeight; cy++) {
260
+ const lineSegments = [];
261
+ let buffer = "";
262
+ let bufferColor;
263
+ for (let cx = 0; cx < charWidth; cx++) {
264
+ const dots = this.fillBrailleDots(pixels, cx, cy, width, height);
265
+ const char = this.createBrailleChar(dots);
266
+ const color = this.resolveColor(pixels, cx, cy, width, height);
267
+ if (buffer && bufferColor !== color) {
268
+ lineSegments.push(
269
+ bufferColor ? { text: buffer, color: bufferColor } : { text: buffer }
270
+ );
271
+ buffer = "";
272
+ }
273
+ bufferColor = color;
274
+ buffer += char;
275
+ }
276
+ if (buffer) {
277
+ lineSegments.push(
278
+ bufferColor ? { text: buffer, color: bufferColor } : { text: buffer }
279
+ );
280
+ }
281
+ lines.push(lineSegments);
282
+ }
283
+ return lines;
284
+ }
285
+ };
286
+
287
+ // src/core/block.ts
288
+ var BlockRenderer = class _BlockRenderer extends Renderer {
289
+ // 9x9 Lookup table: [leftHeight][rightHeight] -> char
290
+ // Index 0-8 represents filled pixel height of column
291
+ static BLOCK_LUT = [
292
+ // R=0 1 2 3 4 5 6 7 8 (L Row Index)
293
+ [" ", "\u2597", "\u2597", "\u2597", "\u2597", "\u2597", "\u2597", "\u2590", "\u2590"],
294
+ // L=0
295
+ ["\u2596", "\u2582", "\u2582", "\u2583", "\u2583", "\u2584", "\u2584", "\u2585", "\u2585"],
296
+ // L=1: Avg 1-1.5 -> ▂
297
+ ["\u2596", "\u2582", "\u2582", "\u2583", "\u2583", "\u2584", "\u2584", "\u2585", "\u2585"],
298
+ // L=2
299
+ ["\u2596", "\u2583", "\u2583", "\u2583", "\u2584", "\u2584", "\u2585", "\u2585", "\u2585"],
300
+ // L=3: Avg 1.5-3.5
301
+ ["\u2596", "\u2583", "\u2583", "\u2584", "\u2584", "\u2585", "\u2585", "\u2586", "\u2586"],
302
+ // L=4
303
+ ["\u2596", "\u2584", "\u2584", "\u2585", "\u2585", "\u2585", "\u2586", "\u2586", "\u2587"],
304
+ // L=5
305
+ ["\u2596", "\u2584", "\u2584", "\u2585", "\u2585", "\u2586", "\u2586", "\u2587", "\u2587"],
306
+ // L=6
307
+ ["\u258C", "\u2585", "\u2585", "\u2586", "\u2586", "\u2587", "\u2587", "\u2587", "\u2588"],
308
+ // L=7
309
+ ["\u258C", "\u2585", "\u2585", "\u2586", "\u2586", "\u2587", "\u2587", "\u2588", "\u2588"]
310
+ // L=8
311
+ ];
312
+ getMetadata() {
313
+ return {
314
+ name: "block",
315
+ displayName: "Block Elements",
316
+ description: "Block Elements character (\u2588) 2x8 matrix, vertical 8x resolution",
317
+ resolution: {
318
+ horizontal: 2,
319
+ vertical: 8
320
+ },
321
+ requiresUtf8: true,
322
+ requiresUnicode: true,
323
+ minScore: 30
324
+ };
325
+ }
326
+ // ============================================================
327
+ // Block-specific private methods
328
+ // ============================================================
329
+ /**
330
+ * Determine primary color
331
+ */
332
+ resolveColor(pixels, startX, startY, width, height) {
333
+ const counts = /* @__PURE__ */ new Map();
334
+ for (let py = 0; py < 8; py++) {
335
+ for (let px = 0; px < 2; px++) {
336
+ const y = startY + py;
337
+ const x = startX + px;
338
+ if (y >= height || x >= width) continue;
339
+ const pixel = pixels[y]?.[x];
340
+ if (pixel?.active && pixel.color) {
341
+ counts.set(pixel.color, (counts.get(pixel.color) ?? 0) + 1);
342
+ }
343
+ }
344
+ }
345
+ if (counts.size === 0) return void 0;
346
+ let maxCount = 0;
347
+ let dominantColor;
348
+ for (const [color, count] of counts) {
349
+ if (count > maxCount) {
350
+ maxCount = count;
351
+ dominantColor = color;
352
+ }
353
+ }
354
+ return dominantColor;
355
+ }
356
+ // ============================================================
357
+ // Methods that subclasses must implement
358
+ // ============================================================
359
+ renderCanvas(pixels, width, height) {
360
+ const charWidth = Math.ceil(width / 2);
361
+ const charHeight = Math.ceil(height / 8);
362
+ const lines = [];
363
+ for (let cy = 0; cy < charHeight; cy++) {
364
+ const lineSegments = [];
365
+ let buffer = "";
366
+ let bufferFg;
367
+ let bufferBg;
368
+ let bufferColorKey;
369
+ for (let cx = 0; cx < charWidth; cx++) {
370
+ const startY = cy * 8;
371
+ const leftX = cx * 2;
372
+ const rightX = cx * 2 + 1;
373
+ const analyzeColumn = (colX) => {
374
+ let count = 0;
375
+ let sumY = 0;
376
+ for (let py = 0; py < 8; py++) {
377
+ const y = startY + py;
378
+ if (y >= height) break;
379
+ if (pixels[y]?.[colX]?.active) {
380
+ count++;
381
+ sumY += py;
382
+ }
383
+ }
384
+ const gravity = count > 0 ? sumY / count : 3.5;
385
+ return { count, gravity };
386
+ };
387
+ const left = analyzeColumn(leftX);
388
+ const right = analyzeColumn(rightX);
389
+ const isLeftTop = left.count > 0 && left.gravity < 3.5;
390
+ const isRightTop = right.count > 0 && right.gravity < 3.5;
391
+ const useInverse = isLeftTop && (isRightTop || right.count === 0) || isRightTop && (isLeftTop || left.count === 0);
392
+ let char = " ";
393
+ let isInverted = false;
394
+ const pixelColor = this.resolveColor(pixels, leftX, startY, width, height);
395
+ if (useInverse) {
396
+ const invLeft = 8 - left.count;
397
+ const invRight = 8 - right.count;
398
+ char = _BlockRenderer.BLOCK_LUT[invLeft]?.[invRight] ?? " ";
399
+ isInverted = true;
400
+ } else {
401
+ const finalLeft = Math.min(8, Math.max(0, left.count));
402
+ const finalRight = Math.min(8, Math.max(0, right.count));
403
+ char = _BlockRenderer.BLOCK_LUT[finalLeft]?.[finalRight] ?? " ";
404
+ }
405
+ let fgColor;
406
+ let bgColor;
407
+ if (char !== " ") {
408
+ if (isInverted) {
409
+ fgColor = "black";
410
+ bgColor = pixelColor;
411
+ } else {
412
+ fgColor = pixelColor;
413
+ bgColor = void 0;
414
+ }
415
+ }
416
+ const colorKey = `${fgColor}|${bgColor}`;
417
+ if (buffer && bufferColorKey !== colorKey) {
418
+ lineSegments.push({
419
+ text: buffer,
420
+ ...bufferFg ? { color: bufferFg } : {},
421
+ ...bufferBg ? { backgroundColor: bufferBg } : {}
422
+ });
423
+ buffer = "";
424
+ }
425
+ bufferColorKey = colorKey;
426
+ bufferFg = fgColor;
427
+ bufferBg = bgColor;
428
+ buffer += char;
429
+ }
430
+ if (buffer) {
431
+ lineSegments.push({
432
+ text: buffer,
433
+ ...bufferFg ? { color: bufferFg } : {},
434
+ ...bufferBg ? { backgroundColor: bufferBg } : {}
435
+ });
436
+ }
437
+ lines.push(lineSegments);
438
+ }
439
+ return lines;
440
+ }
441
+ };
442
+
443
+ // src/core/ascii.ts
444
+ var AsciiRenderer = class extends Renderer {
445
+ getMetadata() {
446
+ return {
447
+ name: "ascii",
448
+ displayName: "ASCII",
449
+ description: "ASCII character (. _ - ' / \\ |) 1x3 resolution, maximum compatibility",
450
+ resolution: {
451
+ horizontal: 1,
452
+ vertical: 3
453
+ },
454
+ requiresUtf8: false,
455
+ requiresUnicode: false,
456
+ minScore: 0
457
+ // No requirements, usable in any terminal
458
+ };
459
+ }
460
+ // ============================================================
461
+ // ASCII-specific private methods
462
+ // ============================================================
463
+ /**
464
+ * Check if the pixel at the specified position is set
465
+ */
466
+ isPixelSet(pixels, x, y) {
467
+ const row = pixels[y];
468
+ if (!row) return false;
469
+ return row[x]?.active ?? false;
470
+ }
471
+ getCellPositions(pixels, x, baseY) {
472
+ const positions = [];
473
+ for (let offset = 0; offset < 3; offset++) {
474
+ if (this.isPixelSet(pixels, x, baseY + offset)) {
475
+ positions.push(offset);
476
+ }
477
+ }
478
+ return positions;
479
+ }
480
+ getNeighborPosition(pixels, x, baseY) {
481
+ const positions = this.getCellPositions(pixels, x, baseY);
482
+ if (positions.length === 0) {
483
+ return null;
484
+ }
485
+ const sum = positions.reduce((total, value) => total + value, 0);
486
+ return sum / positions.length;
487
+ }
488
+ selectMultiPixelChar(hasAnyLeft, hasAnyRight) {
489
+ return hasAnyLeft || hasAnyRight ? "+" : "|";
490
+ }
491
+ selectHorizontalChar(pos) {
492
+ if (pos === 0) {
493
+ return "'";
494
+ }
495
+ if (pos === 1) {
496
+ return "-";
497
+ }
498
+ return "_";
499
+ }
500
+ selectDiagonalChar(params) {
501
+ const { pos, hasAnyLeft, hasAnyRight, leftPos, rightPos } = params;
502
+ if (hasAnyLeft && leftPos !== null && leftPos !== pos) {
503
+ return pos < leftPos ? "/" : "\\";
504
+ }
505
+ if (hasAnyRight && rightPos !== null && rightPos !== pos) {
506
+ return pos > rightPos ? "/" : "\\";
507
+ }
508
+ return null;
509
+ }
510
+ selectIsolatedChar(pos) {
511
+ if (pos === 0) {
512
+ return '"';
513
+ }
514
+ if (pos === 1) {
515
+ return "+";
516
+ }
517
+ return ".";
518
+ }
519
+ selectSinglePixelChar(params) {
520
+ const { pos, hasSameHorizontal, hasAnyLeft, hasAnyRight, leftPos, rightPos } = params;
521
+ if (hasSameHorizontal) {
522
+ return this.selectHorizontalChar(pos);
523
+ }
524
+ const diagonal = this.selectDiagonalChar({
525
+ pos,
526
+ hasAnyLeft,
527
+ hasAnyRight,
528
+ leftPos,
529
+ rightPos
530
+ });
531
+ if (diagonal) {
532
+ return diagonal;
533
+ }
534
+ return this.selectIsolatedChar(pos);
535
+ }
536
+ /**
537
+ * Intelligently select ASCII character
538
+ * Select appropriate character based on 1x3 vertical pixels and adjacent column connections
539
+ */
540
+ selectAsciiChar(pixels, x, baseY) {
541
+ const positions = this.getCellPositions(pixels, x, baseY);
542
+ if (positions.length === 0) {
543
+ return " ";
544
+ }
545
+ const leftPos = this.getNeighborPosition(pixels, x - 1, baseY);
546
+ const rightPos = this.getNeighborPosition(pixels, x + 1, baseY);
547
+ const hasAnyLeft = leftPos !== null;
548
+ const hasAnyRight = rightPos !== null;
549
+ if (positions.length >= 2) {
550
+ return this.selectMultiPixelChar(hasAnyLeft, hasAnyRight);
551
+ }
552
+ const pos = positions[0] ?? 1;
553
+ const hasLeftSame = this.isPixelSet(pixels, x - 1, baseY + pos);
554
+ const hasRightSame = this.isPixelSet(pixels, x + 1, baseY + pos);
555
+ const hasSameHorizontal = hasLeftSame || hasRightSame;
556
+ return this.selectSinglePixelChar({
557
+ pos,
558
+ hasSameHorizontal,
559
+ hasAnyLeft,
560
+ hasAnyRight,
561
+ leftPos,
562
+ rightPos
563
+ });
564
+ }
565
+ /**
566
+ * Determine the primary color for this character cell
567
+ */
568
+ resolveColor(pixels, x, baseY) {
569
+ const counts = /* @__PURE__ */ new Map();
570
+ for (let offset = 0; offset < 3; offset++) {
571
+ const y = baseY + offset;
572
+ if (y >= pixels.length) continue;
573
+ const pixel = pixels[y]?.[x];
574
+ if (pixel?.active && pixel.color) {
575
+ counts.set(pixel.color, (counts.get(pixel.color) ?? 0) + 1);
576
+ }
577
+ }
578
+ if (counts.size === 0) return void 0;
579
+ let maxCount = 0;
580
+ let dominantColor;
581
+ for (const [color, count] of counts) {
582
+ if (count > maxCount) {
583
+ maxCount = count;
584
+ dominantColor = color;
585
+ }
586
+ }
587
+ return dominantColor;
588
+ }
589
+ // ============================================================
590
+ // Methods that subclasses must implement
591
+ // ============================================================
592
+ renderCanvas(pixels, width, height) {
593
+ const lines = [];
594
+ const charHeight = Math.ceil(height / 3);
595
+ for (let cy = 0; cy < charHeight; cy++) {
596
+ const lineSegments = [];
597
+ const baseY = cy * 3;
598
+ let buffer = "";
599
+ let bufferColor;
600
+ for (let x = 0; x < width; x++) {
601
+ const char = this.selectAsciiChar(pixels, x, baseY);
602
+ const color = char === " " ? void 0 : this.resolveColor(pixels, x, baseY);
603
+ if (buffer && bufferColor !== color) {
604
+ lineSegments.push(
605
+ bufferColor ? { text: buffer, color: bufferColor } : { text: buffer }
606
+ );
607
+ buffer = "";
608
+ }
609
+ bufferColor = color;
610
+ buffer += char;
611
+ }
612
+ if (buffer) {
613
+ lineSegments.push(
614
+ bufferColor ? { text: buffer, color: bufferColor } : { text: buffer }
615
+ );
616
+ }
617
+ lines.push(lineSegments);
618
+ }
619
+ return lines;
620
+ }
621
+ };
622
+
623
+ // src/detect/terminal.ts
624
+ var TerminalDetector = class _TerminalDetector {
625
+ /** Terminal whitelist supporting Braille characters (lowercase) */
626
+ static BRAILLE_SUPPORTED_TERMINALS = [
627
+ "iterm",
628
+ "warp",
629
+ "alacritty",
630
+ "kitty",
631
+ "wezterm",
632
+ "hyper",
633
+ "tabby",
634
+ "rio"
635
+ ];
636
+ /** Environment variable information */
637
+ envInfo;
638
+ constructor(env = process.env) {
639
+ this.envInfo = this.extractEnvInfo(env);
640
+ }
641
+ /**
642
+ * Extract relevant information from environment variables
643
+ * @param env - Environment variable object
644
+ * @returns Environment information
645
+ */
646
+ extractEnvInfo(env) {
647
+ const info = {};
648
+ if (env.LANG !== void 0) {
649
+ info.LANG = env.LANG;
650
+ }
651
+ if (env.TERM !== void 0) {
652
+ info.TERM = env.TERM;
653
+ }
654
+ if (env.COLORTERM !== void 0) {
655
+ info.COLORTERM = env.COLORTERM;
656
+ }
657
+ if (env.TERM_PROGRAM !== void 0) {
658
+ info.TERM_PROGRAM = env.TERM_PROGRAM;
659
+ }
660
+ if (env.TERM_PROGRAM_VERSION !== void 0) {
661
+ info.TERM_PROGRAM_VERSION = env.TERM_PROGRAM_VERSION;
662
+ }
663
+ return info;
664
+ }
665
+ /**
666
+ * Detect UTF-8 support
667
+ * @returns Whether UTF-8 is supported
668
+ */
669
+ checkUtf8Support() {
670
+ const lang = this.envInfo.LANG || "";
671
+ return lang.toUpperCase().includes("UTF-8") || lang.toUpperCase().includes("UTF8");
672
+ }
673
+ /**
674
+ * Detect Unicode support
675
+ * UTF-8 terminals usually support Unicode
676
+ * @returns Whether Unicode is supported
677
+ */
678
+ checkUnicodeSupport() {
679
+ return this.checkUtf8Support();
680
+ }
681
+ /**
682
+ * Detect Braille character support
683
+ * Determine based on terminal program whitelist
684
+ * @returns Whether Braille characters are supported
685
+ */
686
+ checkBrailleSupport() {
687
+ const termProgram = (this.envInfo.TERM_PROGRAM || "").toLowerCase();
688
+ return _TerminalDetector.BRAILLE_SUPPORTED_TERMINALS.some(
689
+ (supportedTerm) => termProgram.includes(supportedTerm)
690
+ );
691
+ }
692
+ /**
693
+ * Detect Block Elements character support
694
+ * Most terminals supporting Unicode also support Block Elements
695
+ * @returns Whether Block Elements are supported
696
+ */
697
+ checkBlockElementsSupport() {
698
+ return this.checkUnicodeSupport();
699
+ }
700
+ /**
701
+ * Detect color support (16 colors or more)
702
+ * @returns Whether colors are supported
703
+ */
704
+ checkColorSupport() {
705
+ const term = this.envInfo.TERM || "";
706
+ const colorterm = this.envInfo.COLORTERM || "";
707
+ if (term.includes("color") || term.includes("256color")) {
708
+ return true;
709
+ }
710
+ if (colorterm.length > 0) {
711
+ return true;
712
+ }
713
+ return false;
714
+ }
715
+ /**
716
+ * Detect true color support (24-bit RGB)
717
+ * @returns Whether true color is supported
718
+ */
719
+ checkTrueColorSupport() {
720
+ const colorterm = this.envInfo.COLORTERM || "";
721
+ return colorterm.toLowerCase() === "truecolor" || colorterm === "24bit";
722
+ }
723
+ /**
724
+ * Calculate comprehensive terminal capability score (0-100)
725
+ * @returns Score
726
+ */
727
+ calculateScore() {
728
+ let score = 0;
729
+ if (this.checkUtf8Support()) {
730
+ score += 20;
731
+ }
732
+ if (this.checkUnicodeSupport()) {
733
+ score += 10;
734
+ }
735
+ if (this.checkBrailleSupport()) {
736
+ score += 30;
737
+ }
738
+ if (this.checkBlockElementsSupport()) {
739
+ score += 10;
740
+ }
741
+ if (this.checkColorSupport()) {
742
+ score += 15;
743
+ }
744
+ if (this.checkTrueColorSupport()) {
745
+ score += 15;
746
+ }
747
+ return Math.min(score, 100);
748
+ }
749
+ /**
750
+ * Detect terminal capabilities
751
+ * @returns Terminal capability information
752
+ */
753
+ detect() {
754
+ const supportsUtf8 = this.checkUtf8Support();
755
+ const supportsUnicode = this.checkUnicodeSupport();
756
+ const supportsBraille = this.checkBrailleSupport();
757
+ const supportsBlockElements = this.checkBlockElementsSupport();
758
+ const supportsColor = this.checkColorSupport();
759
+ const supportsTrueColor = this.checkTrueColorSupport();
760
+ const score = this.calculateScore();
761
+ return {
762
+ supportsUtf8,
763
+ supportsUnicode,
764
+ supportsBraille,
765
+ supportsBlockElements,
766
+ supportsColor,
767
+ supportsTrueColor,
768
+ score
769
+ };
770
+ }
771
+ /**
772
+ * Get environment information
773
+ * @returns Environment information
774
+ */
775
+ getEnvironmentInfo() {
776
+ return { ...this.envInfo };
777
+ }
778
+ };
779
+ var terminalDetector = new TerminalDetector();
780
+
781
+ // src/detect/selector.ts
782
+ var RendererSelector = class {
783
+ /** Terminal detector */
784
+ detector;
785
+ constructor(detector = new TerminalDetector()) {
786
+ this.detector = detector;
787
+ }
788
+ /**
789
+ * Creates a renderer instance of the specified type
790
+ * @param type - Renderer type
791
+ * @returns Renderer instance
792
+ */
793
+ createRenderer(type) {
794
+ switch (type) {
795
+ case "braille":
796
+ return new BrailleRenderer();
797
+ case "block":
798
+ return new BlockRenderer();
799
+ case "ascii":
800
+ return new AsciiRenderer();
801
+ }
802
+ }
803
+ /**
804
+ * Get renderer by type
805
+ * @param type - Renderer type
806
+ * @returns Renderer instance
807
+ */
808
+ getRenderer(type) {
809
+ return this.createRenderer(type);
810
+ }
811
+ /**
812
+ * Check if the renderer meets terminal capability requirements
813
+ * @param renderer - Renderer instance
814
+ * @param capabilities - Terminal capabilities
815
+ * @returns Whether requirements are met
816
+ */
817
+ isRendererSupported(renderer, capabilities) {
818
+ const metadata = renderer.getMetadata();
819
+ if (capabilities.score < metadata.minScore) {
820
+ return false;
821
+ }
822
+ if (metadata.requiresUtf8 && !capabilities.supportsUtf8) {
823
+ return false;
824
+ }
825
+ if (metadata.requiresUnicode && !capabilities.supportsUnicode) {
826
+ return false;
827
+ }
828
+ const rendererName = metadata.name;
829
+ if (rendererName === "braille" && !capabilities.supportsBraille) {
830
+ return false;
831
+ }
832
+ if (rendererName === "block" && !capabilities.supportsBlockElements) {
833
+ return false;
834
+ }
835
+ return true;
836
+ }
837
+ /**
838
+ * Automatically select the best renderer
839
+ *
840
+ * Try in the order of the priority chain, returning the first renderer that meets terminal capability requirements
841
+ * If none are satisfied, fallback to ASCII
842
+ *
843
+ * @param preferredChain - Priority chain (default: ['braille', 'block', 'ascii'])
844
+ * @returns Selected renderer instance
845
+ */
846
+ selectBest(preferredChain = ["braille", "block", "ascii"]) {
847
+ const capabilities = this.detector.detect();
848
+ for (const rendererType of preferredChain) {
849
+ const renderer = this.getRenderer(rendererType);
850
+ if (this.isRendererSupported(renderer, capabilities)) {
851
+ return renderer;
852
+ }
853
+ }
854
+ return this.getRenderer("ascii");
855
+ }
856
+ /**
857
+ * Get terminal capability information
858
+ * @returns Terminal capabilities
859
+ */
860
+ getTerminalCapabilities() {
861
+ return this.detector.detect();
862
+ }
863
+ };
864
+ var rendererSelector = new RendererSelector();
865
+ var defaultSelector = new RendererSelector();
866
+ var defaultContext = {
867
+ selector: defaultSelector,
868
+ getCapabilities: () => defaultSelector.getTerminalCapabilities(),
869
+ getRenderer: (type) => defaultSelector.getRenderer(type),
870
+ selectBest: (chain) => defaultSelector.selectBest(chain)
871
+ };
872
+ var InkHudContext = React6.createContext(defaultContext);
873
+ var InkHudProvider = ({
874
+ detector,
875
+ forceRenderer,
876
+ children
877
+ }) => {
878
+ const value = React6.useMemo(() => {
879
+ const selector = detector ? new RendererSelector(detector) : defaultSelector;
880
+ return {
881
+ selector,
882
+ getCapabilities: () => selector.getTerminalCapabilities(),
883
+ getRenderer: (type) => selector.getRenderer(type),
884
+ selectBest: (chain) => {
885
+ if (forceRenderer) {
886
+ return selector.getRenderer(forceRenderer);
887
+ }
888
+ return selector.selectBest(chain);
889
+ }
890
+ };
891
+ }, [detector, forceRenderer]);
892
+ return /* @__PURE__ */ React6__default.default.createElement(InkHudContext.Provider, { value }, children);
893
+ };
894
+ function useInkHud() {
895
+ return React6.useContext(InkHudContext);
896
+ }
897
+ function useRendererSelector() {
898
+ return useInkHud().selector;
899
+ }
900
+ function createGradient(colors, steps) {
901
+ if (colors.length === 0) {
902
+ return Array(steps).fill((text) => text);
903
+ }
904
+ if (colors.length === 1) {
905
+ const color = colors[0];
906
+ if (color) {
907
+ const colorFn = (text) => chalk__default.default.hex(color)(text);
908
+ return Array(steps).fill(colorFn);
909
+ }
910
+ return Array(steps).fill((text) => text);
911
+ }
912
+ const gradient = tinygradient__default.default(colors);
913
+ const rgbColors = gradient.rgb(steps);
914
+ return rgbColors.map((color) => {
915
+ const hex = color.toHex();
916
+ return (text) => chalk__default.default.hex(hex)(text);
917
+ });
918
+ }
919
+ var ONE_DARK_PALETTES = {
920
+ /** One Dark - Classic theme */
921
+ standard: [
922
+ "#61afef",
923
+ // blue
924
+ "#98c379",
925
+ // green
926
+ "#e5c07b",
927
+ // yellow
928
+ "#c678dd",
929
+ // purple
930
+ "#e06c75",
931
+ // red
932
+ "#56b6c2",
933
+ // cyan
934
+ "#d19a66",
935
+ // orange
936
+ "#abb2bf"
937
+ // gray
938
+ ],
939
+ /** One Dark Vivid - More vibrant variant */
940
+ vivid: [
941
+ "#61afef",
942
+ // blue
943
+ "#98c379",
944
+ // green
945
+ "#e5c07b",
946
+ // yellow
947
+ "#c678dd",
948
+ // purple
949
+ "#e06c75",
950
+ // red
951
+ "#56b6c2",
952
+ // cyan
953
+ "#be5046",
954
+ // dark red
955
+ "#d19a66",
956
+ // orange
957
+ "#528bff"
958
+ // bright blue
959
+ ]
960
+ };
961
+ function assignColors(seriesCount, palette = "one-dark") {
962
+ if (seriesCount === 0) {
963
+ return [];
964
+ }
965
+ let baseColors;
966
+ if (Array.isArray(palette)) {
967
+ baseColors = palette;
968
+ } else {
969
+ switch (palette) {
970
+ case "one-dark-vivid":
971
+ baseColors = ONE_DARK_PALETTES.vivid;
972
+ break;
973
+ default:
974
+ baseColors = ONE_DARK_PALETTES.standard;
975
+ break;
976
+ }
977
+ }
978
+ if (seriesCount <= baseColors.length) {
979
+ return baseColors.slice(0, seriesCount);
980
+ }
981
+ const gradient = tinygradient__default.default(baseColors);
982
+ return gradient.rgb(seriesCount).map((c) => c.toHex());
983
+ }
984
+ function colorToChalk(color) {
985
+ if (color.startsWith("#")) {
986
+ return (text) => chalk__default.default.hex(color)(text);
987
+ }
988
+ if (typeof chalk__default.default[color] === "function") {
989
+ return (text) => chalk__default.default[color](text);
990
+ }
991
+ return (text) => text;
992
+ }
993
+
994
+ // src/theme/ThemeContext.tsx
995
+ var ONE_DARK_THEME = {
996
+ name: "one-dark",
997
+ palette: ONE_DARK_PALETTES.standard,
998
+ semantic: {
999
+ success: "#98c379",
1000
+ // green
1001
+ error: "#e06c75",
1002
+ // red
1003
+ warning: "#e5c07b",
1004
+ // yellow
1005
+ info: "#61afef",
1006
+ // blue
1007
+ muted: "#5c6370",
1008
+ // gray
1009
+ text: "#abb2bf",
1010
+ // light gray
1011
+ textSecondary: "#5c6370"
1012
+ // dark gray
1013
+ },
1014
+ heatmapGradient: [
1015
+ "#282c34",
1016
+ // background (dark)
1017
+ "#56b6c2",
1018
+ // cyan
1019
+ "#98c379",
1020
+ // green
1021
+ "#e5c07b",
1022
+ // yellow
1023
+ "#61afef"
1024
+ // blue (light)
1025
+ ]
1026
+ };
1027
+ var ThemeContext = React6.createContext(ONE_DARK_THEME);
1028
+ var ThemeProvider = ({ theme: customTheme, children }) => {
1029
+ const mergedTheme = React6.useMemo(() => {
1030
+ if (!customTheme) {
1031
+ return ONE_DARK_THEME;
1032
+ }
1033
+ return {
1034
+ ...ONE_DARK_THEME,
1035
+ ...customTheme,
1036
+ semantic: {
1037
+ ...ONE_DARK_THEME.semantic,
1038
+ ...customTheme.semantic
1039
+ }
1040
+ };
1041
+ }, [customTheme]);
1042
+ return /* @__PURE__ */ React6__default.default.createElement(ThemeContext.Provider, { value: mergedTheme }, children);
1043
+ };
1044
+ function useTheme() {
1045
+ return React6.useContext(ThemeContext);
1046
+ }
1047
+ function useSemanticColors() {
1048
+ const theme = useTheme();
1049
+ return theme.semantic;
1050
+ }
1051
+
1052
+ // src/utils/scale.ts
1053
+ function linearScale(value, domain, range) {
1054
+ const [domainMin, domainMax] = domain;
1055
+ const [rangeMin, rangeMax] = range;
1056
+ if (domainMax === domainMin) {
1057
+ return rangeMin;
1058
+ }
1059
+ const ratio = (value - domainMin) / (domainMax - domainMin);
1060
+ return ratio * (rangeMax - rangeMin) + rangeMin;
1061
+ }
1062
+ function normalize(data) {
1063
+ if (data.length === 0) {
1064
+ return [];
1065
+ }
1066
+ const min = Math.min(...data);
1067
+ const max = Math.max(...data);
1068
+ if (max === min) {
1069
+ return data.map(() => 0.5);
1070
+ }
1071
+ return data.map((value) => linearScale(value, [min, max], [0, 1]));
1072
+ }
1073
+ function scaleToRange(data, range) {
1074
+ if (data.length === 0) {
1075
+ return [];
1076
+ }
1077
+ const min = Math.min(...data);
1078
+ const max = Math.max(...data);
1079
+ if (max === min) {
1080
+ const midPoint = (range[0] + range[1]) / 2;
1081
+ return data.map(() => midPoint);
1082
+ }
1083
+ return data.map((value) => linearScale(value, [min, max], range));
1084
+ }
1085
+ function clamp(value, min, max) {
1086
+ return Math.min(Math.max(value, min), max);
1087
+ }
1088
+
1089
+ // src/utils/geometry.ts
1090
+ function midpointCircle(centerX, centerY, radius) {
1091
+ const points = [];
1092
+ let x = 0;
1093
+ let y = radius;
1094
+ let d = 1 - radius;
1095
+ while (x <= y) {
1096
+ points.push(
1097
+ [centerX + x, centerY + y],
1098
+ [centerX - x, centerY + y],
1099
+ [centerX + x, centerY - y],
1100
+ [centerX - x, centerY - y],
1101
+ [centerX + y, centerY + x],
1102
+ [centerX - y, centerY + x],
1103
+ [centerX + y, centerY - x],
1104
+ [centerX - y, centerY - x]
1105
+ );
1106
+ x++;
1107
+ if (d < 0) {
1108
+ d += 2 * x + 1;
1109
+ } else {
1110
+ y--;
1111
+ d += 2 * (x - y) + 1;
1112
+ }
1113
+ }
1114
+ return points;
1115
+ }
1116
+ function pointOnArc(centerX, centerY, radius, angle) {
1117
+ const x = centerX + radius * Math.cos(angle);
1118
+ const y = centerY + radius * Math.sin(angle);
1119
+ return [x, y];
1120
+ }
1121
+ function arcPoints(centerX, centerY, radius, startAngle, endAngle, steps) {
1122
+ const points = [];
1123
+ let start = startAngle;
1124
+ let end = endAngle;
1125
+ if (start > end) {
1126
+ [start, end] = [end, start];
1127
+ }
1128
+ const angleRange = end - start;
1129
+ const stepCount = steps ?? Math.max(10, Math.ceil(radius * angleRange));
1130
+ const angleStep = angleRange / stepCount;
1131
+ for (let i = 0; i <= stepCount; i++) {
1132
+ const angle = start + i * angleStep;
1133
+ points.push(pointOnArc(centerX, centerY, radius, angle));
1134
+ }
1135
+ return points;
1136
+ }
1137
+ function degreesToRadians(degrees) {
1138
+ return degrees * Math.PI / 180;
1139
+ }
1140
+ function radiansToDegrees(radians) {
1141
+ return radians * 180 / Math.PI;
1142
+ }
1143
+ function distanceBetweenPoints(x1, y1, x2, y2) {
1144
+ const dx = x2 - x1;
1145
+ const dy = y2 - y1;
1146
+ return Math.sqrt(dx * dx + dy * dy);
1147
+ }
1148
+
1149
+ // src/utils/downsampling.ts
1150
+ function computeAveragePoint(data, startIndexInclusive, endIndexExclusive) {
1151
+ const count = endIndexExclusive - startIndexInclusive;
1152
+ if (count <= 0) {
1153
+ return { x: 0, y: 0 };
1154
+ }
1155
+ let sumX = 0;
1156
+ let sumY = 0;
1157
+ for (let i = startIndexInclusive; i < endIndexExclusive; i++) {
1158
+ sumX += i;
1159
+ sumY += data[i] ?? 0;
1160
+ }
1161
+ return { x: sumX / count, y: sumY / count };
1162
+ }
1163
+ function triangleArea(prevX, prevY, avgX, avgY, pointX, pointY) {
1164
+ return Math.abs((prevX - avgX) * (pointY - prevY) - (prevX - pointX) * (avgY - prevY));
1165
+ }
1166
+ function findLargestTrianglePointIndex(data, bucketStart, bucketEnd, prevX, prevY, avgX, avgY) {
1167
+ let maxArea = -1;
1168
+ let maxIdx = bucketStart;
1169
+ for (let i = bucketStart; i < bucketEnd; i++) {
1170
+ const area = triangleArea(prevX, prevY, avgX, avgY, i, data[i] ?? 0);
1171
+ if (area > maxArea) {
1172
+ maxArea = area;
1173
+ maxIdx = i;
1174
+ }
1175
+ }
1176
+ return maxIdx;
1177
+ }
1178
+ function lttb(data, threshold) {
1179
+ if (data.length <= threshold) {
1180
+ return data;
1181
+ }
1182
+ if (threshold <= 2) {
1183
+ return [data[0] ?? 0, data[data.length - 1] ?? 0];
1184
+ }
1185
+ const sampled = [data[0] ?? 0];
1186
+ let prevIndex = 0;
1187
+ const bucketSize = (data.length - 2) / (threshold - 2);
1188
+ for (let i = 0; i < threshold - 2; i++) {
1189
+ const bucketStart = Math.floor(i * bucketSize) + 1;
1190
+ const bucketEnd = Math.floor((i + 1) * bucketSize) + 1;
1191
+ const nextBucketStart = bucketEnd;
1192
+ const nextBucketEnd = Math.min(Math.floor((i + 2) * bucketSize) + 1, data.length);
1193
+ const { x: avgX, y: avgY } = computeAveragePoint(data, nextBucketStart, nextBucketEnd);
1194
+ const prevX = prevIndex;
1195
+ const prevY = data[prevIndex] ?? 0;
1196
+ const maxIdx = findLargestTrianglePointIndex(
1197
+ data,
1198
+ bucketStart,
1199
+ bucketEnd,
1200
+ prevX,
1201
+ prevY,
1202
+ avgX,
1203
+ avgY
1204
+ );
1205
+ sampled.push(data[maxIdx] ?? 0);
1206
+ prevIndex = maxIdx;
1207
+ }
1208
+ sampled.push(data[data.length - 1] ?? 0);
1209
+ return sampled;
1210
+ }
1211
+ function fixedIntervalDownsampling(data, threshold) {
1212
+ if (data.length <= threshold) {
1213
+ return data;
1214
+ }
1215
+ const sampled = [];
1216
+ const step = (data.length - 1) / (threshold - 1);
1217
+ for (let i = 0; i < threshold; i++) {
1218
+ const index = Math.round(i * step);
1219
+ sampled.push(data[index] ?? 0);
1220
+ }
1221
+ return sampled;
1222
+ }
1223
+ function averageDownsampling(data, threshold) {
1224
+ if (data.length <= threshold) {
1225
+ return data;
1226
+ }
1227
+ const sampled = [];
1228
+ const bucketSize = data.length / threshold;
1229
+ for (let i = 0; i < threshold; i++) {
1230
+ const bucketStart = Math.floor(i * bucketSize);
1231
+ const bucketEnd = Math.floor((i + 1) * bucketSize);
1232
+ let sum = 0;
1233
+ let count = 0;
1234
+ for (let j = bucketStart; j < bucketEnd; j++) {
1235
+ sum += data[j] ?? 0;
1236
+ count++;
1237
+ }
1238
+ sampled.push(count > 0 ? sum / count : 0);
1239
+ }
1240
+ return sampled;
1241
+ }
1242
+ function minMaxDownsampling(data, threshold) {
1243
+ if (data.length <= threshold) {
1244
+ return data;
1245
+ }
1246
+ const sampled = [];
1247
+ const bucketSize = data.length / threshold;
1248
+ for (let i = 0; i < threshold; i++) {
1249
+ const bucketStart = Math.floor(i * bucketSize);
1250
+ const bucketEnd = Math.floor((i + 1) * bucketSize);
1251
+ let min = Number.POSITIVE_INFINITY;
1252
+ let max = Number.NEGATIVE_INFINITY;
1253
+ for (let j = bucketStart; j < bucketEnd; j++) {
1254
+ const value = data[j] ?? 0;
1255
+ if (value < min) min = value;
1256
+ if (value > max) max = value;
1257
+ }
1258
+ sampled.push(min, max);
1259
+ }
1260
+ return sampled;
1261
+ }
1262
+ function easeInOutQuad(t) {
1263
+ return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
1264
+ }
1265
+ function easeLinear(t) {
1266
+ return t;
1267
+ }
1268
+ function easeOutCubic(t) {
1269
+ const t1 = t - 1;
1270
+ return t1 * t1 * t1 + 1;
1271
+ }
1272
+ function easeInCubic(t) {
1273
+ return t * t * t;
1274
+ }
1275
+ function useSmooth(targetValue, duration = 300, easingFn = easeInOutQuad) {
1276
+ const [currentValue, setCurrentValue] = React6.useState(targetValue);
1277
+ const startValueRef = React6.useRef(targetValue);
1278
+ const startTimeRef = React6.useRef(null);
1279
+ const timerRef = React6.useRef(null);
1280
+ React6.useEffect(() => {
1281
+ if (targetValue === currentValue) {
1282
+ return;
1283
+ }
1284
+ startValueRef.current = currentValue;
1285
+ startTimeRef.current = Date.now();
1286
+ const frameInterval = 1e3 / 30;
1287
+ const animate = () => {
1288
+ const now = Date.now();
1289
+ const elapsed = now - (startTimeRef.current ?? now);
1290
+ const progress = Math.min(elapsed / duration, 1);
1291
+ const easedProgress = easingFn(progress);
1292
+ const newValue = startValueRef.current + (targetValue - startValueRef.current) * easedProgress;
1293
+ setCurrentValue(newValue);
1294
+ if (progress < 1) {
1295
+ timerRef.current = setTimeout(animate, frameInterval);
1296
+ }
1297
+ };
1298
+ timerRef.current = setTimeout(animate, frameInterval);
1299
+ return () => {
1300
+ if (timerRef.current !== null) {
1301
+ clearTimeout(timerRef.current);
1302
+ }
1303
+ };
1304
+ }, [targetValue, duration, easingFn, currentValue]);
1305
+ return currentValue;
1306
+ }
1307
+ function useSmoothArray(targetData, duration = 300, easingFn = easeInOutQuad) {
1308
+ const [currentData, setCurrentData] = React6.useState(targetData);
1309
+ const startDataRef = React6.useRef(targetData);
1310
+ const startTimeRef = React6.useRef(null);
1311
+ const timerRef = React6.useRef(null);
1312
+ React6.useEffect(() => {
1313
+ if (targetData.length !== currentData.length) {
1314
+ setCurrentData(targetData);
1315
+ return;
1316
+ }
1317
+ const hasChanged = targetData.some((value, i) => value !== currentData[i]);
1318
+ if (!hasChanged) {
1319
+ return;
1320
+ }
1321
+ startDataRef.current = currentData;
1322
+ startTimeRef.current = Date.now();
1323
+ const frameInterval = 1e3 / 30;
1324
+ const animate = () => {
1325
+ const now = Date.now();
1326
+ const elapsed = now - (startTimeRef.current ?? now);
1327
+ const progress = Math.min(elapsed / duration, 1);
1328
+ const easedProgress = easingFn(progress);
1329
+ const newData = targetData.map((target, i) => {
1330
+ const start = startDataRef.current[i] ?? 0;
1331
+ return start + (target - start) * easedProgress;
1332
+ });
1333
+ setCurrentData(newData);
1334
+ if (progress < 1) {
1335
+ timerRef.current = setTimeout(animate, frameInterval);
1336
+ }
1337
+ };
1338
+ timerRef.current = setTimeout(animate, frameInterval);
1339
+ return () => {
1340
+ if (timerRef.current !== null) {
1341
+ clearTimeout(timerRef.current);
1342
+ }
1343
+ };
1344
+ }, [targetData, duration, easingFn, currentData]);
1345
+ return currentData;
1346
+ }
1347
+ function useThrottle(value, fps = 30) {
1348
+ const [throttledValue, setThrottledValue] = React6.useState(value);
1349
+ const lastUpdateRef = React6.useRef(Date.now());
1350
+ React6.useEffect(() => {
1351
+ const now = Date.now();
1352
+ const interval = 1e3 / fps;
1353
+ const elapsed = now - lastUpdateRef.current;
1354
+ if (elapsed >= interval) {
1355
+ setThrottledValue(value);
1356
+ lastUpdateRef.current = now;
1357
+ return;
1358
+ }
1359
+ const timeoutId = setTimeout(() => {
1360
+ setThrottledValue(value);
1361
+ lastUpdateRef.current = Date.now();
1362
+ }, interval - elapsed);
1363
+ return () => clearTimeout(timeoutId);
1364
+ }, [value, fps]);
1365
+ return throttledValue;
1366
+ }
1367
+ var GridContext = React6.createContext({
1368
+ columns: 12,
1369
+ gap: 0,
1370
+ totalWidth: 80
1371
+ // Fallback
1372
+ });
1373
+ var GridItemContext = React6.createContext(null);
1374
+ var Grid = ({
1375
+ columns = 12,
1376
+ gap = 0,
1377
+ width: propsWidth,
1378
+ widthOffset = 0,
1379
+ rowHeight,
1380
+ children
1381
+ }) => {
1382
+ const { stdout } = ink.useStdout();
1383
+ const [terminalWidth, setTerminalWidth] = React6.useState(stdout ? stdout.columns : 80);
1384
+ React6.useEffect(() => {
1385
+ if (!stdout) return;
1386
+ const onResize = () => setTerminalWidth(stdout.columns);
1387
+ stdout.on("resize", onResize);
1388
+ return () => {
1389
+ stdout.off("resize", onResize);
1390
+ };
1391
+ }, [stdout]);
1392
+ const defaultWidth = terminalWidth > 2 ? terminalWidth - 2 : terminalWidth;
1393
+ const totalWidth = propsWidth ?? Math.max(0, defaultWidth - widthOffset);
1394
+ return /* @__PURE__ */ React6__default.default.createElement(GridContext.Provider, { value: { columns, gap, totalWidth, rowHeight } }, /* @__PURE__ */ React6__default.default.createElement(
1395
+ ink.Box,
1396
+ {
1397
+ flexDirection: "row",
1398
+ flexWrap: "wrap",
1399
+ columnGap: gap,
1400
+ rowGap: gap,
1401
+ width: totalWidth
1402
+ },
1403
+ children
1404
+ ));
1405
+ };
1406
+ var GridItem = ({
1407
+ span = 1,
1408
+ height,
1409
+ minHeight,
1410
+ overflow,
1411
+ children
1412
+ }) => {
1413
+ const { columns, gap, totalWidth, rowHeight: contextRowHeight } = React6.useContext(GridContext);
1414
+ const totalGapWidth = Math.max(0, (columns - 1) * gap);
1415
+ const availableWidth = Math.max(0, totalWidth - totalGapWidth);
1416
+ const colWidth = availableWidth / columns;
1417
+ const itemGapWidth = Math.max(0, (span - 1) * gap);
1418
+ const basisWidth = Math.floor(colWidth * span + itemGapWidth);
1419
+ const effectiveHeight = height ?? contextRowHeight;
1420
+ return /* @__PURE__ */ React6__default.default.createElement(GridItemContext.Provider, { value: { width: basisWidth, height: effectiveHeight } }, /* @__PURE__ */ React6__default.default.createElement(
1421
+ ink.Box,
1422
+ {
1423
+ flexGrow: 1,
1424
+ flexShrink: 1,
1425
+ flexBasis: basisWidth,
1426
+ flexDirection: "column",
1427
+ ...effectiveHeight !== void 0 && { height: effectiveHeight },
1428
+ ...minHeight !== void 0 && { minHeight },
1429
+ ...overflow !== void 0 && { overflow }
1430
+ },
1431
+ children
1432
+ ));
1433
+ };
1434
+
1435
+ // src/components/common/chartUtils.ts
1436
+ function resolveSeriesColors(series, colors, palette) {
1437
+ if (series.length === 0) {
1438
+ return [];
1439
+ }
1440
+ if (colors && colors.length >= series.length) {
1441
+ return colors.slice(0, series.length);
1442
+ }
1443
+ if (colors && colors.length > 0) {
1444
+ return assignColors(series.length, colors);
1445
+ }
1446
+ return assignColors(series.length, palette);
1447
+ }
1448
+ function useChartLayout(props, config) {
1449
+ const {
1450
+ minWidth = 10,
1451
+ widthOffset = 0,
1452
+ heightOffset = 0,
1453
+ showXAxis = true,
1454
+ showYAxis = true,
1455
+ showLegend = true,
1456
+ legendPosition = "right",
1457
+ xAxisLabel,
1458
+ yTickCount = 5,
1459
+ yTickFormat,
1460
+ defaultWidth = 60,
1461
+ min,
1462
+ max
1463
+ } = config;
1464
+ const gridContext = React6.useContext(GridItemContext);
1465
+ const totalHeight = props.height ?? (typeof gridContext?.height === "number" ? gridContext.height : 15);
1466
+ let totalWidth = props.width;
1467
+ if (totalWidth === void 0) {
1468
+ if (gridContext?.width) {
1469
+ totalWidth = Math.max(minWidth, gridContext.width - widthOffset);
1470
+ } else {
1471
+ totalWidth = defaultWidth;
1472
+ }
1473
+ }
1474
+ const yAxisWidth = showYAxis ? getYAxisLabelWidth({
1475
+ min,
1476
+ max,
1477
+ tickCount: yTickCount,
1478
+ tickFormat: yTickFormat ?? defaultTickFormat
1479
+ }) : 0;
1480
+ const effectiveXAxisHeight = showXAxis ? 1 + (xAxisLabel ? 1 : 0) : 0;
1481
+ let legendWidth = 0;
1482
+ let legendHeight = 0;
1483
+ if (showLegend) {
1484
+ if (legendPosition === "right") {
1485
+ legendWidth = 20;
1486
+ } else {
1487
+ legendHeight = 2;
1488
+ }
1489
+ }
1490
+ const yAxisSpacing = showYAxis ? 1 : 0;
1491
+ const legendSpacing = showLegend && legendPosition === "right" ? 2 : 0;
1492
+ const plotWidth = Math.max(
1493
+ 1,
1494
+ totalWidth - yAxisWidth - yAxisSpacing - legendWidth - legendSpacing
1495
+ );
1496
+ const plotHeight = Math.max(
1497
+ 1,
1498
+ totalHeight - heightOffset - legendHeight - effectiveXAxisHeight
1499
+ );
1500
+ return {
1501
+ totalWidth,
1502
+ totalHeight,
1503
+ plotWidth,
1504
+ plotHeight,
1505
+ yAxisWidth,
1506
+ legendHeight,
1507
+ legendWidth,
1508
+ xAxisHeight: effectiveXAxisHeight
1509
+ };
1510
+ }
1511
+ function useChartLayoutSimple(props, min, max) {
1512
+ const {
1513
+ width: propsWidth,
1514
+ height: propsHeight,
1515
+ showAxis = true,
1516
+ showXAxis,
1517
+ showYAxis,
1518
+ showLegend = true,
1519
+ legendPosition = "right",
1520
+ xAxisLabel,
1521
+ yAxisLabel,
1522
+ yTickCount = 5,
1523
+ yTickFormat,
1524
+ widthOffset = 0,
1525
+ heightOffset = 0
1526
+ } = props;
1527
+ const renderXAxis = showXAxis ?? showAxis;
1528
+ const renderYAxis = showYAxis ?? showAxis;
1529
+ return useChartLayout(
1530
+ {
1531
+ ...propsWidth !== void 0 && { width: propsWidth },
1532
+ ...propsHeight !== void 0 && { height: propsHeight }
1533
+ },
1534
+ {
1535
+ widthOffset,
1536
+ heightOffset,
1537
+ showXAxis: renderXAxis,
1538
+ showYAxis: renderYAxis,
1539
+ showLegend,
1540
+ legendPosition,
1541
+ ...xAxisLabel && { xAxisLabel },
1542
+ ...yAxisLabel && { yAxisLabel },
1543
+ yTickCount,
1544
+ ...yTickFormat && { yTickFormat },
1545
+ min,
1546
+ max
1547
+ }
1548
+ );
1549
+ }
1550
+ function buildSeriesInputParams(series, data, seriesName) {
1551
+ const params = {};
1552
+ if (series !== void 0) {
1553
+ params.series = series;
1554
+ }
1555
+ if (data !== void 0) {
1556
+ params.data = data;
1557
+ }
1558
+ if (seriesName !== void 0) {
1559
+ params.seriesName = seriesName;
1560
+ }
1561
+ return params;
1562
+ }
1563
+ function resolveSeriesInput(params) {
1564
+ const { series, data, seriesName } = params;
1565
+ if (series && series.length > 0) {
1566
+ return series;
1567
+ }
1568
+ if (data && data.length > 0) {
1569
+ return [
1570
+ {
1571
+ name: seriesName ?? "Series",
1572
+ data
1573
+ }
1574
+ ];
1575
+ }
1576
+ return [];
1577
+ }
1578
+ function computeSeriesExtent(series) {
1579
+ let min = 0;
1580
+ let max = 0;
1581
+ let maxLength = 0;
1582
+ let hasValue = false;
1583
+ for (const item of series) {
1584
+ maxLength = Math.max(maxLength, item.data.length);
1585
+ for (const value of item.data) {
1586
+ if (!hasValue) {
1587
+ min = value;
1588
+ max = value;
1589
+ hasValue = true;
1590
+ continue;
1591
+ }
1592
+ min = Math.min(min, value);
1593
+ max = Math.max(max, value);
1594
+ }
1595
+ }
1596
+ if (!hasValue) {
1597
+ return { min: 0, max: 0, maxLength };
1598
+ }
1599
+ return { min, max, maxLength };
1600
+ }
1601
+ function getPixelDimensions(renderer, width, height) {
1602
+ const resolution = renderer.getResolution();
1603
+ return {
1604
+ pixelWidth: width * resolution.horizontal,
1605
+ pixelHeight: height * resolution.vertical
1606
+ };
1607
+ }
1608
+ function defaultTickFormat(value) {
1609
+ const absValue = Math.abs(value);
1610
+ const sign = value < 0 ? "-" : "";
1611
+ if (absValue >= 1e9) {
1612
+ const v = absValue / 1e9;
1613
+ return `${sign}${Number.isInteger(v) ? v : v.toFixed(1)}b`;
1614
+ }
1615
+ if (absValue >= 1e6) {
1616
+ const v = absValue / 1e6;
1617
+ return `${sign}${Number.isInteger(v) ? v : v.toFixed(1)}m`;
1618
+ }
1619
+ if (absValue >= 1e3) {
1620
+ const v = absValue / 1e3;
1621
+ return `${sign}${Number.isInteger(v) ? v : v.toFixed(1)}k`;
1622
+ }
1623
+ return Math.round(value).toString();
1624
+ }
1625
+ function getYAxisLabelWidth(params) {
1626
+ const { min, max, tickCount, tickFormat = defaultTickFormat } = params;
1627
+ if (tickCount <= 0) {
1628
+ return 0;
1629
+ }
1630
+ if (max === min) {
1631
+ return tickFormat(min).length;
1632
+ }
1633
+ const step = (max - min) / (tickCount - 1);
1634
+ let maxLength = 0;
1635
+ for (let i = 0; i < tickCount; i++) {
1636
+ const value = min + i * step;
1637
+ maxLength = Math.max(maxLength, tickFormat(value).length);
1638
+ }
1639
+ return maxLength;
1640
+ }
1641
+ function computeBaselineY(params) {
1642
+ const { min, max, pixelHeight } = params;
1643
+ if (max === min) {
1644
+ return Math.round((pixelHeight - 1) / 2);
1645
+ }
1646
+ const baselineValue = min <= 0 && max >= 0 ? 0 : min > 0 ? min : max;
1647
+ return Math.round(linearScale(baselineValue, [min, max], [pixelHeight - 1, 0]));
1648
+ }
1649
+
1650
+ // src/components/common/Axis.tsx
1651
+ var Axis = ({
1652
+ type,
1653
+ min,
1654
+ max,
1655
+ tickCount = 5,
1656
+ tickFormat = defaultTickFormat,
1657
+ label,
1658
+ length,
1659
+ color = "gray",
1660
+ showGrid: _showGrid = false,
1661
+ integerScale = false
1662
+ }) => {
1663
+ const ticks = React6.useMemo(() => {
1664
+ if (tickCount <= 0) {
1665
+ return [];
1666
+ }
1667
+ if (max === min) {
1668
+ return [{ value: min, position: 0 }];
1669
+ }
1670
+ let effectiveTickCount = tickCount;
1671
+ let step = (max - min) / (tickCount - 1);
1672
+ if (integerScale) {
1673
+ const range = max - min;
1674
+ if (range < tickCount - 1) {
1675
+ effectiveTickCount = range + 1;
1676
+ step = 1;
1677
+ }
1678
+ }
1679
+ return Array.from({ length: effectiveTickCount }, (_, i) => {
1680
+ let value = min + i * step;
1681
+ if (integerScale) {
1682
+ value = Math.round(value);
1683
+ }
1684
+ const position = (value - min) / (max - min) * length;
1685
+ return { value, position };
1686
+ });
1687
+ }, [min, max, tickCount, length, integerScale]);
1688
+ if (type === "x") {
1689
+ const xConfig = (() => {
1690
+ let previousEnd = 0;
1691
+ const elements = [];
1692
+ ticks.forEach((tick, i) => {
1693
+ const tickLabel = tickFormat(tick.value);
1694
+ const labelWidth = tickLabel.length;
1695
+ const intendedStart = Math.floor(tick.position - labelWidth / 2);
1696
+ const actualStart = Math.max(previousEnd, intendedStart);
1697
+ const spaces = Math.max(0, actualStart - previousEnd);
1698
+ if (spaces > 0) {
1699
+ elements.push(/* @__PURE__ */ React6__default.default.createElement(ink.Text, { key: `space-${i}` }, " ".repeat(spaces)));
1700
+ }
1701
+ elements.push(
1702
+ /* @__PURE__ */ React6__default.default.createElement(ink.Text, { key: `tick-${i}`, color, wrap: "truncate" }, tickLabel)
1703
+ );
1704
+ previousEnd = actualStart + labelWidth;
1705
+ });
1706
+ return elements;
1707
+ })();
1708
+ return /* @__PURE__ */ React6__default.default.createElement(ink.Box, { flexDirection: "column" }, /* @__PURE__ */ React6__default.default.createElement(ink.Box, { flexDirection: "row" }, xConfig), label && /* @__PURE__ */ React6__default.default.createElement(ink.Box, { justifyContent: "center", marginTop: 0 }, /* @__PURE__ */ React6__default.default.createElement(ink.Text, { color, dimColor: true }, label)));
1709
+ }
1710
+ return /* @__PURE__ */ React6__default.default.createElement(
1711
+ ink.Box,
1712
+ {
1713
+ flexDirection: "column",
1714
+ alignItems: "flex-end",
1715
+ height: length,
1716
+ justifyContent: "space-between"
1717
+ },
1718
+ ticks.slice().reverse().map((tick, i) => /* @__PURE__ */ React6__default.default.createElement(ink.Box, { key: `tick-${i}` }, /* @__PURE__ */ React6__default.default.createElement(ink.Text, { color, wrap: "truncate" }, tickFormat(tick.value))))
1719
+ );
1720
+ };
1721
+ var Legend = ({
1722
+ items,
1723
+ position = "horizontal",
1724
+ color,
1725
+ gap = 2
1726
+ }) => {
1727
+ if (items.length === 0) {
1728
+ return null;
1729
+ }
1730
+ return /* @__PURE__ */ React6__default.default.createElement(ink.Box, { flexDirection: position === "horizontal" ? "row" : "column", gap }, items.map((item, i) => /* @__PURE__ */ React6__default.default.createElement(ink.Box, { key: `legend-${i}`, gap: 1 }, /* @__PURE__ */ React6__default.default.createElement(ink.Text, { color: color ?? item.color }, item.symbol || "\u25CF"), /* @__PURE__ */ React6__default.default.createElement(ink.Text, { ...color && { color } }, item.name))));
1731
+ };
1732
+
1733
+ // src/components/common/ChartContainer.tsx
1734
+ var ChartContainer = ({
1735
+ layout,
1736
+ showXAxis = true,
1737
+ showYAxis = true,
1738
+ xAxisConfig,
1739
+ yAxisConfig,
1740
+ showLegend = true,
1741
+ legendPosition = "right",
1742
+ legendItems = [],
1743
+ children
1744
+ }) => {
1745
+ const { totalWidth, plotWidth, plotHeight, yAxisWidth } = layout;
1746
+ return /* @__PURE__ */ React6__default.default.createElement(ink.Box, { flexDirection: "column", width: totalWidth }, showLegend && legendPosition === "top" && /* @__PURE__ */ React6__default.default.createElement(ink.Box, { marginBottom: 1, marginLeft: showYAxis ? yAxisWidth + 1 : 0 }, /* @__PURE__ */ React6__default.default.createElement(Legend, { items: legendItems, position: "horizontal" })), /* @__PURE__ */ React6__default.default.createElement(ink.Box, { flexDirection: "row" }, showYAxis && yAxisConfig && /* @__PURE__ */ React6__default.default.createElement(ink.Box, { marginRight: 1, width: yAxisWidth }, /* @__PURE__ */ React6__default.default.createElement(Axis, { type: "y", length: plotHeight, ...yAxisConfig })), /* @__PURE__ */ React6__default.default.createElement(ink.Box, { flexDirection: "column" }, children), showLegend && legendPosition === "right" && /* @__PURE__ */ React6__default.default.createElement(ink.Box, { marginLeft: 2 }, /* @__PURE__ */ React6__default.default.createElement(Legend, { items: legendItems, position: "vertical" }))), showXAxis && xAxisConfig && /* @__PURE__ */ React6__default.default.createElement(ink.Box, { marginLeft: showYAxis ? yAxisWidth + 1 : 0 }, /* @__PURE__ */ React6__default.default.createElement(Axis, { type: "x", length: plotWidth, ...xAxisConfig })), showLegend && legendPosition === "bottom" && /* @__PURE__ */ React6__default.default.createElement(ink.Box, { marginTop: 1, marginLeft: showYAxis ? yAxisWidth + 1 : 0 }, /* @__PURE__ */ React6__default.default.createElement(Legend, { items: legendItems, position: "horizontal" })));
1747
+ };
1748
+ function useChartCore(props) {
1749
+ const { series: seriesProp, data, seriesName, colors: colorsProp, colorPalette } = props;
1750
+ const series = React6.useMemo(
1751
+ () => resolveSeriesInput(buildSeriesInputParams(seriesProp, data, seriesName)),
1752
+ [seriesProp, data, seriesName]
1753
+ );
1754
+ const { min, max, maxLength } = React6.useMemo(() => computeSeriesExtent(series), [series]);
1755
+ const colors = React6.useMemo(
1756
+ () => resolveSeriesColors(series, colorsProp, colorPalette),
1757
+ [series, colorsProp, colorPalette]
1758
+ );
1759
+ const legendItems = React6.useMemo(
1760
+ () => series.map((item, i) => ({
1761
+ name: item.name,
1762
+ color: item.color ?? colors[i] ?? "cyan",
1763
+ symbol: "\u25CF"
1764
+ })),
1765
+ [series, colors]
1766
+ );
1767
+ return {
1768
+ series,
1769
+ min,
1770
+ max,
1771
+ maxLength,
1772
+ colors,
1773
+ legendItems
1774
+ };
1775
+ }
1776
+ var DEFAULT_RENDERER_CHAIN = ["braille", "block", "ascii"];
1777
+ var BAR_CHART_RENDERER_CHAIN = ["block", "braille", "ascii"];
1778
+ function useChartRenderer(props, defaultChain = DEFAULT_RENDERER_CHAIN) {
1779
+ const { getRenderer, selectBest } = useInkHud();
1780
+ const { renderer: preferredRenderer, rendererChain = defaultChain } = props;
1781
+ return React6.useMemo(() => {
1782
+ if (preferredRenderer) {
1783
+ return getRenderer(preferredRenderer);
1784
+ }
1785
+ return selectBest(rendererChain);
1786
+ }, [preferredRenderer, rendererChain, getRenderer, selectBest]);
1787
+ }
1788
+
1789
+ // src/components/LineChart.tsx
1790
+ var LineChart = (props) => {
1791
+ const {
1792
+ showLegend = true,
1793
+ showAxis = true,
1794
+ showXAxis,
1795
+ showYAxis,
1796
+ legendPosition = "right",
1797
+ xAxisLabel,
1798
+ yAxisLabel,
1799
+ xTickCount = 5,
1800
+ yTickCount = 5,
1801
+ xTickFormat,
1802
+ yTickFormat,
1803
+ rendererChain = DEFAULT_RENDERER_CHAIN,
1804
+ xIntegerScale = true,
1805
+ yIntegerScale = false
1806
+ } = props;
1807
+ const renderXAxis = showXAxis ?? showAxis;
1808
+ const renderYAxis = showYAxis ?? showAxis;
1809
+ const { series, min, max, maxLength, colors, legendItems } = useChartCore(props);
1810
+ const renderer = useChartRenderer(props, rendererChain);
1811
+ const layout = useChartLayoutSimple(props, min, max);
1812
+ const { plotWidth: canvasWidth, plotHeight: canvasHeight } = layout;
1813
+ const coloredLines = React6.useMemo(
1814
+ () => renderLineChartCanvas({
1815
+ renderer,
1816
+ series,
1817
+ canvasWidth,
1818
+ canvasHeight,
1819
+ min,
1820
+ max,
1821
+ colors
1822
+ }),
1823
+ [renderer, series, canvasWidth, canvasHeight, min, max, colors]
1824
+ );
1825
+ if (coloredLines.length === 0) {
1826
+ return null;
1827
+ }
1828
+ const yAxisConfig = {
1829
+ min,
1830
+ max,
1831
+ tickCount: yTickCount,
1832
+ tickFormat: yTickFormat ?? defaultTickFormat,
1833
+ ...yIntegerScale !== void 0 && { integerScale: yIntegerScale },
1834
+ ...yAxisLabel ? { label: yAxisLabel } : {}
1835
+ };
1836
+ const xAxisConfig = {
1837
+ min: 0,
1838
+ max: Math.max(0, maxLength - 1),
1839
+ tickCount: xTickCount,
1840
+ tickFormat: xTickFormat ?? defaultTickFormat,
1841
+ ...xIntegerScale !== void 0 && { integerScale: xIntegerScale },
1842
+ ...xAxisLabel ? { label: xAxisLabel } : {}
1843
+ };
1844
+ return /* @__PURE__ */ React6__default.default.createElement(
1845
+ ChartContainer,
1846
+ {
1847
+ layout,
1848
+ showLegend,
1849
+ legendPosition,
1850
+ legendItems,
1851
+ showXAxis: renderXAxis,
1852
+ showYAxis: renderYAxis,
1853
+ xAxisConfig,
1854
+ yAxisConfig
1855
+ },
1856
+ coloredLines.map((line, i) => /* @__PURE__ */ React6__default.default.createElement(ink.Text, { key: i }, line.map((seg, j) => /* @__PURE__ */ React6__default.default.createElement(
1857
+ ink.Text,
1858
+ {
1859
+ key: j,
1860
+ ...seg.color ? { color: seg.color } : {},
1861
+ ...seg.backgroundColor ? { backgroundColor: seg.backgroundColor } : {}
1862
+ },
1863
+ seg.text
1864
+ ))))
1865
+ );
1866
+ };
1867
+ function renderLineChartCanvas({
1868
+ renderer,
1869
+ series,
1870
+ canvasWidth,
1871
+ canvasHeight,
1872
+ min,
1873
+ max,
1874
+ colors
1875
+ }) {
1876
+ const { pixelWidth, pixelHeight } = getPixelDimensions(renderer, canvasWidth, canvasHeight);
1877
+ if (pixelWidth <= 0 || pixelHeight <= 0 || series.length === 0) {
1878
+ return [];
1879
+ }
1880
+ const canvas = renderer.createCanvas(pixelWidth, pixelHeight);
1881
+ for (let si = 0; si < series.length; si++) {
1882
+ const s = series[si];
1883
+ if (!s) continue;
1884
+ const { data } = s;
1885
+ const color = s.color ?? colors[si] ?? "cyan";
1886
+ if (data.length === 0) continue;
1887
+ const xScale = data.length > 1 ? (pixelWidth - 1) / (data.length - 1) : 0;
1888
+ const scaleY = (value) => {
1889
+ if (max === min) {
1890
+ return Math.round((pixelHeight - 1) / 2);
1891
+ }
1892
+ return Math.round(linearScale(value, [min, max], [pixelHeight - 1, 0]));
1893
+ };
1894
+ for (let i = 0; i < data.length - 1; i++) {
1895
+ const currVal = data[i];
1896
+ const nextVal = data[i + 1];
1897
+ if (currVal === void 0 || nextVal === void 0) continue;
1898
+ const x0 = Math.round(i * xScale);
1899
+ const y0 = scaleY(currVal);
1900
+ const x1 = Math.round((i + 1) * xScale);
1901
+ const y1 = scaleY(nextVal);
1902
+ renderer.drawLine(canvas, x0, y0, x1, y1, { active: true, color });
1903
+ }
1904
+ if (data.length === 1) {
1905
+ const val = data[0];
1906
+ if (val !== void 0) {
1907
+ const x = Math.round(pixelWidth / 2);
1908
+ const y = scaleY(val);
1909
+ renderer.setPixel(canvas, x, y, { active: true, color });
1910
+ }
1911
+ }
1912
+ }
1913
+ return renderer.renderCanvas(canvas, pixelWidth, pixelHeight);
1914
+ }
1915
+ function fillVerticalLine(renderer, canvas, x, y1, y2, color) {
1916
+ const fillStart = Math.min(y1, y2);
1917
+ const fillEnd = Math.max(y1, y2);
1918
+ for (let fy = fillStart; fy <= fillEnd; fy++) {
1919
+ renderer.setPixel(canvas, x, fy, {
1920
+ active: true,
1921
+ ...color ? { color } : {}
1922
+ });
1923
+ }
1924
+ }
1925
+ function renderAreaChartCanvas(params) {
1926
+ const { renderer, series, canvasWidth, canvasHeight, min, max, maxLength, colors } = params;
1927
+ if (series.length === 0 || maxLength === 0) {
1928
+ return [];
1929
+ }
1930
+ const { pixelWidth, pixelHeight } = getPixelDimensions(renderer, canvasWidth, canvasHeight);
1931
+ const canvas = renderer.createCanvas(pixelWidth, pixelHeight);
1932
+ const baselineY = computeBaselineY({ min, max, pixelHeight });
1933
+ const xStep = maxLength > 1 ? (pixelWidth - 1) / (maxLength - 1) : 0;
1934
+ const scaleValue = (value) => {
1935
+ if (max === min) {
1936
+ return Math.round((pixelHeight - 1) / 2);
1937
+ }
1938
+ return Math.round(linearScale(value, [min, max], [pixelHeight - 1, 0]));
1939
+ };
1940
+ const sortedData = series.map((s, i) => {
1941
+ const maxVal = Math.max(...s.data);
1942
+ return { series: s, color: colors[i], maxVal };
1943
+ }).sort((a, b) => b.maxVal - a.maxVal);
1944
+ for (const { series: item, color } of sortedData) {
1945
+ let prevPoint = null;
1946
+ for (let idx = 0; idx < item.data.length; idx++) {
1947
+ const value = item.data[idx];
1948
+ if (value === void 0) continue;
1949
+ const point = { x: Math.round(idx * xStep), y: scaleValue(value) };
1950
+ if (prevPoint) {
1951
+ const startX = Math.min(prevPoint.x, point.x);
1952
+ const endX = Math.max(prevPoint.x, point.x);
1953
+ for (let px = startX; px <= endX; px++) {
1954
+ const t = endX === startX ? 0 : (px - prevPoint.x) / (point.x - prevPoint.x);
1955
+ const py = Math.floor(prevPoint.y + (point.y - prevPoint.y) * t);
1956
+ fillVerticalLine(renderer, canvas, px, py, baselineY, color);
1957
+ }
1958
+ renderer.drawLine(canvas, prevPoint.x, prevPoint.y, point.x, point.y, {
1959
+ active: true,
1960
+ ...color ? { color } : {}
1961
+ });
1962
+ } else {
1963
+ fillVerticalLine(renderer, canvas, point.x, point.y, baselineY, color);
1964
+ }
1965
+ prevPoint = point;
1966
+ }
1967
+ }
1968
+ return renderer.renderCanvas(canvas, pixelWidth, pixelHeight);
1969
+ }
1970
+ var AreaChart = (props) => {
1971
+ const {
1972
+ showLegend = true,
1973
+ showAxis = true,
1974
+ showXAxis,
1975
+ showYAxis,
1976
+ legendPosition = "right",
1977
+ xAxisLabel,
1978
+ yAxisLabel,
1979
+ xTickCount = 5,
1980
+ yTickCount = 5,
1981
+ xTickFormat,
1982
+ yTickFormat,
1983
+ rendererChain = DEFAULT_RENDERER_CHAIN,
1984
+ xIntegerScale = true,
1985
+ yIntegerScale = false
1986
+ } = props;
1987
+ const renderXAxis = showXAxis ?? showAxis;
1988
+ const renderYAxis = showYAxis ?? showAxis;
1989
+ const { series, min, max, maxLength, colors, legendItems } = useChartCore(props);
1990
+ const renderer = useChartRenderer(props, rendererChain);
1991
+ const layout = useChartLayoutSimple(props, min, max);
1992
+ const { plotWidth: canvasWidth, plotHeight: canvasHeight } = layout;
1993
+ const coloredLines = React6.useMemo(
1994
+ () => renderAreaChartCanvas({
1995
+ renderer,
1996
+ series,
1997
+ canvasWidth,
1998
+ canvasHeight,
1999
+ min,
2000
+ max,
2001
+ maxLength,
2002
+ colors
2003
+ }),
2004
+ [renderer, series, canvasWidth, canvasHeight, min, max, maxLength, colors]
2005
+ );
2006
+ if (coloredLines.length === 0) {
2007
+ return null;
2008
+ }
2009
+ const yAxisConfig = {
2010
+ min,
2011
+ max,
2012
+ tickCount: yTickCount,
2013
+ tickFormat: yTickFormat ?? defaultTickFormat,
2014
+ ...yIntegerScale !== void 0 && { integerScale: yIntegerScale },
2015
+ ...yAxisLabel ? { label: yAxisLabel } : {}
2016
+ };
2017
+ const xAxisConfig = {
2018
+ min: 0,
2019
+ max: Math.max(0, maxLength - 1),
2020
+ tickCount: xTickCount,
2021
+ tickFormat: xTickFormat ?? defaultTickFormat,
2022
+ ...xIntegerScale !== void 0 && { integerScale: xIntegerScale },
2023
+ ...xAxisLabel ? { label: xAxisLabel } : {}
2024
+ };
2025
+ return /* @__PURE__ */ React6__default.default.createElement(
2026
+ ChartContainer,
2027
+ {
2028
+ layout,
2029
+ showXAxis: renderXAxis,
2030
+ showYAxis: renderYAxis,
2031
+ xAxisConfig,
2032
+ yAxisConfig,
2033
+ showLegend,
2034
+ legendPosition,
2035
+ legendItems
2036
+ },
2037
+ coloredLines.map((segments, i) => /* @__PURE__ */ React6__default.default.createElement(ink.Text, { key: `chart-line-${i}` }, segments.map(
2038
+ (segment, j) => segment.color ? /* @__PURE__ */ React6__default.default.createElement(ink.Text, { key: `seg-${i}-${j}`, color: segment.color }, segment.text) : /* @__PURE__ */ React6__default.default.createElement(ink.Text, { key: `seg-${i}-${j}` }, segment.text)
2039
+ )))
2040
+ );
2041
+ };
2042
+ var alignDown = (val, alignment) => Math.floor(val / alignment) * alignment;
2043
+ function computeVerticalLayout(params) {
2044
+ const { pixelWidth, categoryCount, seriesCount, alignment } = params;
2045
+ const maxGroupWidth = alignDown(
2046
+ Math.max(alignment, Math.floor(pixelWidth / Math.max(1, categoryCount))),
2047
+ alignment
2048
+ );
2049
+ const groupWidth = maxGroupWidth;
2050
+ const calculateForPadding = (p) => {
2051
+ const available = groupWidth - p * 2;
2052
+ const minNeed = seriesCount * alignment;
2053
+ if (available < minNeed) return null;
2054
+ const gap = available >= seriesCount * alignment + (seriesCount - 1) * alignment ? alignment : 0;
2055
+ const totalGap = gap * (seriesCount - 1);
2056
+ const remaining = available - totalGap;
2057
+ let barWidth = Math.floor(remaining / seriesCount);
2058
+ barWidth = alignDown(barWidth, alignment);
2059
+ if (barWidth < alignment) return null;
2060
+ return { groupWidth, barWidth, barGap: gap, groupPadding: p };
2061
+ };
2062
+ const paddingStandard = alignment;
2063
+ const layoutStandard = calculateForPadding(paddingStandard);
2064
+ const layoutCompact = calculateForPadding(0);
2065
+ if (!layoutStandard) {
2066
+ return layoutCompact ?? { groupWidth, barWidth: alignment, barGap: 0, groupPadding: 0 };
2067
+ }
2068
+ if (!layoutCompact) {
2069
+ return layoutStandard;
2070
+ }
2071
+ if (layoutCompact.barWidth > layoutStandard.barWidth) {
2072
+ return layoutCompact;
2073
+ }
2074
+ return layoutStandard;
2075
+ }
2076
+ function computeHorizontalLayout(params) {
2077
+ const { pixelHeight, categoryCount, seriesCount, alignment } = params;
2078
+ const maxGroupHeight = alignDown(
2079
+ Math.max(alignment, Math.floor(pixelHeight / Math.max(1, categoryCount))),
2080
+ alignment
2081
+ );
2082
+ if (alignment >= 4) {
2083
+ const stackHeight = seriesCount * alignment;
2084
+ const groupHeight = Math.max(alignment, stackHeight);
2085
+ return {
2086
+ groupHeight,
2087
+ // Strictly tight
2088
+ barHeight: alignment,
2089
+ // Min thickness (1 block char)
2090
+ barGap: 0,
2091
+ // No gaps
2092
+ groupPadding: 0
2093
+ // No padding
2094
+ };
2095
+ }
2096
+ const padding = alignment;
2097
+ const available = maxGroupHeight - padding * 2;
2098
+ if (available > seriesCount * alignment * 1.5) {
2099
+ const gap = alignment;
2100
+ const barHeight = alignDown(
2101
+ Math.floor((available - gap * (seriesCount - 1)) / seriesCount),
2102
+ alignment
2103
+ );
2104
+ return { groupHeight: maxGroupHeight, barHeight, barGap: gap, groupPadding: padding };
2105
+ }
2106
+ const availableCompact = maxGroupHeight;
2107
+ const barHeightCompact = alignDown(Math.floor(availableCompact / seriesCount), alignment);
2108
+ return { groupHeight: maxGroupHeight, barHeight: barHeightCompact, barGap: 0, groupPadding: 0 };
2109
+ }
2110
+ function renderVertical(params) {
2111
+ const { renderer, series, width, height, min, max, maxLength, colors } = params;
2112
+ const { pixelWidth, pixelHeight } = getPixelDimensions(renderer, width, height);
2113
+ const canvas = renderer.createCanvas(pixelWidth, pixelHeight);
2114
+ const resolution = renderer.getResolution();
2115
+ const alignment = resolution.horizontal;
2116
+ const { groupWidth, barWidth, barGap, groupPadding } = computeVerticalLayout({
2117
+ pixelWidth,
2118
+ categoryCount: maxLength,
2119
+ seriesCount: series.length,
2120
+ alignment
2121
+ });
2122
+ const baselineY = computeBaselineY({ min, max, pixelHeight });
2123
+ const scaleValue = (val) => {
2124
+ if (max === min) return Math.round((pixelHeight - 1) / 2);
2125
+ return Math.round(linearScale(val, [min, max], [pixelHeight - 1, 0]));
2126
+ };
2127
+ for (let i = 0; i < maxLength; i++) {
2128
+ const groupLeft = i * groupWidth + groupPadding;
2129
+ for (let j = 0; j < series.length; j++) {
2130
+ const val = series[j]?.data[i] ?? 0;
2131
+ const yVal = scaleValue(val);
2132
+ const x = groupLeft + j * (barWidth + barGap);
2133
+ const xEnd = Math.min(pixelWidth - 1, x + barWidth - 1);
2134
+ const yStart = Math.min(yVal, baselineY);
2135
+ const yEnd = Math.max(yVal, baselineY);
2136
+ const color = colors[j];
2137
+ for (let yy = yStart; yy <= yEnd; yy++) {
2138
+ for (let xx = x; xx <= xEnd; xx++) {
2139
+ renderer.setPixel(canvas, xx, yy, {
2140
+ active: true,
2141
+ ...color ? { color } : {}
2142
+ });
2143
+ }
2144
+ }
2145
+ }
2146
+ }
2147
+ return renderer.renderCanvas(canvas, pixelWidth, pixelHeight);
2148
+ }
2149
+ function renderHorizontal(params) {
2150
+ const { renderer, series, width, height, min, max, maxLength, colors } = params;
2151
+ const resolution = renderer.getResolution();
2152
+ const pixelWidth = width * resolution.horizontal;
2153
+ const pixelHeight = height * resolution.vertical;
2154
+ const canvas = renderer.createCanvas(pixelWidth, pixelHeight);
2155
+ const alignment = resolution.vertical;
2156
+ const { groupHeight, barHeight, barGap, groupPadding } = computeHorizontalLayout({
2157
+ pixelHeight,
2158
+ categoryCount: maxLength,
2159
+ seriesCount: series.length,
2160
+ alignment
2161
+ });
2162
+ const scaleValue = (val) => {
2163
+ if (max === min) return Math.round((pixelWidth - 1) / 2);
2164
+ return Math.round(linearScale(val, [min, max], [0, pixelWidth - 1]));
2165
+ };
2166
+ let baselineVal = 0;
2167
+ if (min > 0) baselineVal = min;
2168
+ else if (max < 0) baselineVal = max;
2169
+ const baselineX = scaleValue(baselineVal);
2170
+ for (let i = 0; i < maxLength; i++) {
2171
+ const groupTop = i * groupHeight + groupPadding;
2172
+ for (let j = 0; j < series.length; j++) {
2173
+ const val = series[j]?.data[i] ?? 0;
2174
+ const xVal = scaleValue(val);
2175
+ const y = groupTop + j * (barHeight + barGap);
2176
+ const yEnd = Math.min(pixelHeight - 1, y + barHeight - 1);
2177
+ if (y >= pixelHeight) continue;
2178
+ const xStart = Math.min(xVal, baselineX);
2179
+ const xEndFill = Math.max(xVal, baselineX);
2180
+ const color = colors[j];
2181
+ for (let yy = y; yy <= yEnd; yy++) {
2182
+ for (let xx = xStart; xx <= xEndFill; xx++) {
2183
+ renderer.setPixel(canvas, xx, yy, {
2184
+ active: true,
2185
+ ...color ? { color } : {}
2186
+ });
2187
+ }
2188
+ }
2189
+ }
2190
+ }
2191
+ return renderer.renderCanvas(canvas, pixelWidth, pixelHeight);
2192
+ }
2193
+ var BarChart = (props) => {
2194
+ const {
2195
+ showLegend = true,
2196
+ showAxis = true,
2197
+ showXAxis,
2198
+ showYAxis,
2199
+ legendPosition = "right",
2200
+ orientation = "vertical",
2201
+ xAxisLabel,
2202
+ yAxisLabel,
2203
+ xTickCount = 5,
2204
+ yTickCount = 5,
2205
+ xTickFormat,
2206
+ yTickFormat,
2207
+ rendererChain = BAR_CHART_RENDERER_CHAIN,
2208
+ xIntegerScale,
2209
+ yIntegerScale
2210
+ } = props;
2211
+ const renderXAxis = showXAxis ?? showAxis;
2212
+ const renderYAxis = showYAxis ?? showAxis;
2213
+ const { series, min, max, maxLength, colors, legendItems } = useChartCore(props);
2214
+ const renderer = useChartRenderer(props, rendererChain);
2215
+ const layout = useChartLayoutSimple(props, min, max);
2216
+ const { plotWidth: canvasWidth, plotHeight: canvasHeight } = layout;
2217
+ const coloredLines = React6.useMemo(() => {
2218
+ if (series.length === 0 || maxLength === 0) return [];
2219
+ if (orientation === "horizontal") {
2220
+ return renderHorizontal({
2221
+ renderer,
2222
+ series,
2223
+ width: canvasWidth,
2224
+ height: canvasHeight,
2225
+ min,
2226
+ max,
2227
+ maxLength,
2228
+ colors
2229
+ });
2230
+ }
2231
+ return renderVertical({
2232
+ renderer,
2233
+ series,
2234
+ width: canvasWidth,
2235
+ height: canvasHeight,
2236
+ min,
2237
+ max,
2238
+ maxLength,
2239
+ colors
2240
+ });
2241
+ }, [renderer, series, canvasWidth, canvasHeight, min, max, maxLength, colors, orientation]);
2242
+ if (coloredLines.length === 0) return null;
2243
+ const yAxisConfig = {
2244
+ min,
2245
+ max,
2246
+ tickCount: yTickCount,
2247
+ tickFormat: yTickFormat ?? defaultTickFormat,
2248
+ ...yIntegerScale !== void 0 && { integerScale: yIntegerScale },
2249
+ ...yAxisLabel ? { label: yAxisLabel } : {}
2250
+ };
2251
+ const xAxisConfig = {
2252
+ min: 0,
2253
+ max: Math.max(0, maxLength - 1),
2254
+ tickCount: xTickCount,
2255
+ tickFormat: xTickFormat ?? defaultTickFormat,
2256
+ integerScale: xIntegerScale ?? true,
2257
+ ...xAxisLabel ? { label: xAxisLabel } : {}
2258
+ };
2259
+ return /* @__PURE__ */ React6__default.default.createElement(
2260
+ ChartContainer,
2261
+ {
2262
+ layout,
2263
+ showXAxis: renderXAxis,
2264
+ showYAxis: renderYAxis,
2265
+ xAxisConfig,
2266
+ yAxisConfig,
2267
+ showLegend,
2268
+ legendPosition,
2269
+ legendItems
2270
+ },
2271
+ coloredLines.map((segments, i) => /* @__PURE__ */ React6__default.default.createElement(ink.Text, { key: `chart-line-${i}` }, segments.map((segment, j) => /* @__PURE__ */ React6__default.default.createElement(
2272
+ ink.Text,
2273
+ {
2274
+ key: `seg-${i}-${j}`,
2275
+ ...segment.color ? { color: segment.color } : {},
2276
+ ...segment.backgroundColor ? { backgroundColor: segment.backgroundColor } : {}
2277
+ },
2278
+ segment.text
2279
+ ))))
2280
+ );
2281
+ };
2282
+ function resolveDataItems(data, labels) {
2283
+ if (!data || data.length === 0) {
2284
+ return [];
2285
+ }
2286
+ if (typeof data[0] === "number") {
2287
+ return data.map((value, i) => ({
2288
+ name: labels?.[i] ?? `Item ${i + 1}`,
2289
+ value
2290
+ }));
2291
+ }
2292
+ return data;
2293
+ }
2294
+ var TWO_PI = Math.PI * 2;
2295
+ var START_ANGLE = -Math.PI / 2;
2296
+ function resolveRadius(pixelWidth, pixelHeight, customRadius) {
2297
+ const centerX = Math.floor(pixelWidth / 2);
2298
+ const centerY = Math.floor(pixelHeight / 2);
2299
+ const maxRadius = Math.max(
2300
+ 0,
2301
+ Math.min(Math.floor(pixelWidth / 2) - 1, Math.floor(pixelHeight / 2) - 1)
2302
+ );
2303
+ const radius = customRadius ?? maxRadius;
2304
+ return { centerX, centerY, radius };
2305
+ }
2306
+ function buildAngleStops(data, total) {
2307
+ if (total <= 0) {
2308
+ return [];
2309
+ }
2310
+ const stops = [];
2311
+ let current = 0;
2312
+ data.forEach((item, index) => {
2313
+ if (item.value <= 0) {
2314
+ return;
2315
+ }
2316
+ current += item.value / total * TWO_PI;
2317
+ stops.push({ index, end: current });
2318
+ });
2319
+ return stops;
2320
+ }
2321
+ function resolveSliceIndex(angle, stops) {
2322
+ if (stops.length === 0) {
2323
+ return null;
2324
+ }
2325
+ for (const stop of stops) {
2326
+ if (angle <= stop.end) {
2327
+ return stop.index;
2328
+ }
2329
+ }
2330
+ return stops[stops.length - 1]?.index ?? null;
2331
+ }
2332
+ var PieChart = ({
2333
+ data: dataProp,
2334
+ labels,
2335
+ width,
2336
+ height,
2337
+ radius: customRadius,
2338
+ aspectRatio,
2339
+ donutRatio = 0,
2340
+ showLabels = false,
2341
+ showLegend = true,
2342
+ legendPosition = "right",
2343
+ colors,
2344
+ colorPalette,
2345
+ renderer: preferredRenderer,
2346
+ rendererChain = ["braille", "block", "ascii"],
2347
+ heightOffset = 0,
2348
+ widthOffset = 0
2349
+ }) => {
2350
+ const data = React6.useMemo(() => resolveDataItems(dataProp, labels), [dataProp, labels]);
2351
+ const layout = useChartLayoutSimple(
2352
+ {
2353
+ ...width !== void 0 && { width },
2354
+ ...height !== void 0 && { height },
2355
+ showAxis: false,
2356
+ showLegend,
2357
+ legendPosition,
2358
+ widthOffset,
2359
+ heightOffset
2360
+ },
2361
+ 0,
2362
+ 0
2363
+ );
2364
+ const { totalWidth, plotWidth: canvasWidth, plotHeight: canvasHeight } = layout;
2365
+ const { getRenderer, selectBest } = useInkHud();
2366
+ const renderer = React6.useMemo(() => {
2367
+ if (preferredRenderer) {
2368
+ return getRenderer(preferredRenderer);
2369
+ }
2370
+ return selectBest(rendererChain);
2371
+ }, [preferredRenderer, rendererChain, getRenderer, selectBest]);
2372
+ const ratio = React6.useMemo(() => {
2373
+ if (aspectRatio !== void 0) return aspectRatio;
2374
+ const resolution = renderer.getResolution();
2375
+ return 2 * (resolution.horizontal / resolution.vertical);
2376
+ }, [aspectRatio, renderer]);
2377
+ const itemColors = React6.useMemo(() => {
2378
+ if (colors && colors.length >= data.length) {
2379
+ return colors;
2380
+ }
2381
+ if (colors && colors.length > 0) {
2382
+ return assignColors(data.length, colors);
2383
+ }
2384
+ return assignColors(data.length, colorPalette);
2385
+ }, [data.length, colors, colorPalette]);
2386
+ const { total, percentages } = React6.useMemo(() => {
2387
+ const sum = data.reduce((acc, item) => acc + item.value, 0);
2388
+ const pcts = data.map((item) => sum > 0 ? item.value / sum * 100 : 0);
2389
+ return { total: sum, percentages: pcts };
2390
+ }, [data]);
2391
+ const coloredLines = React6.useMemo(() => {
2392
+ if (data.length === 0 || total <= 0) {
2393
+ return [];
2394
+ }
2395
+ const { pixelWidth, pixelHeight } = getPixelDimensions(renderer, canvasWidth, canvasHeight);
2396
+ const canvas = renderer.createCanvas(pixelWidth, pixelHeight);
2397
+ const {
2398
+ centerX,
2399
+ centerY,
2400
+ radius: outerRadius
2401
+ } = resolveRadius(pixelWidth, pixelHeight, customRadius);
2402
+ const innerRadius = outerRadius * donutRatio;
2403
+ const angleStops = buildAngleStops(data, total);
2404
+ for (let y = 0; y < pixelHeight; y++) {
2405
+ const dy = (y - centerY) * ratio;
2406
+ for (let x = 0; x < pixelWidth; x++) {
2407
+ const dx = x - centerX;
2408
+ const distance = Math.sqrt(dx * dx + dy * dy);
2409
+ if (distance > outerRadius || distance < innerRadius) {
2410
+ continue;
2411
+ }
2412
+ const angle = (Math.atan2(dy, dx) - START_ANGLE + TWO_PI) % TWO_PI;
2413
+ const sliceIndex = resolveSliceIndex(angle, angleStops);
2414
+ if (sliceIndex !== null) {
2415
+ const color = data[sliceIndex]?.color ?? itemColors[sliceIndex];
2416
+ if (color) {
2417
+ renderer.setPixel(canvas, x, y, { active: true, color });
2418
+ }
2419
+ }
2420
+ }
2421
+ }
2422
+ return renderer.renderCanvas(canvas, pixelWidth, pixelHeight);
2423
+ }, [
2424
+ data,
2425
+ total,
2426
+ renderer,
2427
+ canvasWidth,
2428
+ canvasHeight,
2429
+ customRadius,
2430
+ ratio,
2431
+ donutRatio,
2432
+ itemColors
2433
+ ]);
2434
+ const legendItems = React6.useMemo(() => {
2435
+ return data.map((item, i) => ({
2436
+ name: showLabels ? `${item.name} (${percentages[i]?.toFixed(1)}%)` : item.name,
2437
+ color: item.color ?? itemColors[i] ?? "cyan",
2438
+ symbol: "\u25CF"
2439
+ }));
2440
+ }, [data, itemColors, showLabels, percentages]);
2441
+ if (coloredLines.length === 0) {
2442
+ return null;
2443
+ }
2444
+ return /* @__PURE__ */ React6__default.default.createElement(ink.Box, { flexDirection: "column", width: totalWidth, alignItems: "center" }, /* @__PURE__ */ React6__default.default.createElement(ink.Box, { flexDirection: "row" }, /* @__PURE__ */ React6__default.default.createElement(ink.Box, { flexDirection: "column" }, coloredLines.map((segments, i) => /* @__PURE__ */ React6__default.default.createElement(ink.Text, { key: `chart-line-${i}` }, segments.map((segment, j) => /* @__PURE__ */ React6__default.default.createElement(
2445
+ ink.Text,
2446
+ {
2447
+ key: `seg-${i}-${j}`,
2448
+ ...segment.color ? { color: segment.color } : {},
2449
+ ...segment.backgroundColor ? { backgroundColor: segment.backgroundColor } : {}
2450
+ },
2451
+ segment.text
2452
+ ))))), showLegend && legendPosition === "right" && /* @__PURE__ */ React6__default.default.createElement(ink.Box, { marginLeft: 2 }, /* @__PURE__ */ React6__default.default.createElement(Legend, { items: legendItems, position: "vertical" }))), showLegend && legendPosition === "bottom" && /* @__PURE__ */ React6__default.default.createElement(ink.Box, { marginTop: 1 }, /* @__PURE__ */ React6__default.default.createElement(Legend, { items: legendItems, position: "horizontal" })));
2453
+ };
2454
+ var SPARK_LEVELS_BLOCK = [" ", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
2455
+ var SPARK_LEVELS_BRAILLE = ["\u2800", "\u2840", "\u28C0", "\u28C4", "\u28E4", "\u28E6", "\u28F6", "\u28F7", "\u28FF"];
2456
+ var SPARK_LEVELS_ASCII = [" ", ".", ":", "-", "=", "+", "*", "#", "%", "@"];
2457
+ var Sparkline = ({
2458
+ data,
2459
+ width: propsWidth,
2460
+ min: userMin,
2461
+ max: userMax,
2462
+ color,
2463
+ variant = "block"
2464
+ }) => {
2465
+ const gridContext = React6.useContext(GridItemContext);
2466
+ const effectiveWidth = propsWidth ?? gridContext?.width;
2467
+ const text = React6.useMemo(() => {
2468
+ if (!data || data.length === 0) return "";
2469
+ let processedData = data;
2470
+ if (effectiveWidth && data.length > effectiveWidth) {
2471
+ processedData = lttb(data, effectiveWidth);
2472
+ }
2473
+ const min = userMin ?? Math.min(...processedData);
2474
+ let max = userMax ?? Math.max(...processedData);
2475
+ if (max === min) {
2476
+ max = min + 1;
2477
+ }
2478
+ const levels = variant === "braille" ? SPARK_LEVELS_BRAILLE : variant === "ascii" ? SPARK_LEVELS_ASCII : SPARK_LEVELS_BLOCK;
2479
+ return processedData.map((v) => {
2480
+ const value = Math.max(min, Math.min(max, v));
2481
+ const normalized = (value - min) / (max - min);
2482
+ const index = Math.round(normalized * (levels.length - 1));
2483
+ return levels[index];
2484
+ }).join("");
2485
+ }, [data, effectiveWidth, userMin, userMax, variant]);
2486
+ return /* @__PURE__ */ React6__default.default.createElement(ink.Text, { ...color ? { color } : {} }, text);
2487
+ };
2488
+ var Panel = ({
2489
+ title,
2490
+ titleAlignment = "left",
2491
+ borderStyle = "round",
2492
+ borderColor,
2493
+ padding = 0,
2494
+ width,
2495
+ height,
2496
+ children
2497
+ }) => {
2498
+ const gridContext = React6__default.default.useContext(GridItemContext);
2499
+ const effectiveWidth = width ?? gridContext?.width;
2500
+ const effectiveHeight = height ?? gridContext?.height;
2501
+ const borderOverhead = borderStyle ? 2 : 0;
2502
+ const paddingOverhead = padding * 2;
2503
+ const totalOverhead = borderOverhead + paddingOverhead;
2504
+ const innerWidth = typeof effectiveWidth === "number" ? Math.max(0, effectiveWidth - totalOverhead) : void 0;
2505
+ const innerHeight = typeof effectiveHeight === "number" ? Math.max(0, effectiveHeight - totalOverhead) : void 0;
2506
+ const boxProps = {
2507
+ borderStyle,
2508
+ flexDirection: "column",
2509
+ ...effectiveWidth !== void 0 && { width: effectiveWidth },
2510
+ ...effectiveHeight !== void 0 && { height: effectiveHeight },
2511
+ ...borderColor !== void 0 && { borderColor }
2512
+ };
2513
+ const childContext = React6__default.default.useMemo(
2514
+ () => ({
2515
+ ...innerWidth !== void 0 ? { width: innerWidth } : {},
2516
+ height: innerHeight
2517
+ }),
2518
+ [innerWidth, innerHeight]
2519
+ );
2520
+ return /* @__PURE__ */ React6__default.default.createElement(GridItemContext.Provider, { value: childContext }, /* @__PURE__ */ React6__default.default.createElement(ink.Box, { ...boxProps }, title && /* @__PURE__ */ React6__default.default.createElement(
2521
+ ink.Box,
2522
+ {
2523
+ position: "absolute",
2524
+ marginTop: -1,
2525
+ width: "100%",
2526
+ paddingX: 2,
2527
+ justifyContent: titleAlignment === "center" ? "center" : titleAlignment === "right" ? "flex-end" : "flex-start"
2528
+ },
2529
+ /* @__PURE__ */ React6__default.default.createElement(ink.Text, { bold: true, color: borderColor || "white" }, " ", title, " ")
2530
+ ), /* @__PURE__ */ React6__default.default.createElement(ink.Box, { padding, flexDirection: "column", flexGrow: 1 }, children)));
2531
+ };
2532
+ var CHAR_SETS = {
2533
+ unicode: { fill: "\u2588", empty: "\u2591" },
2534
+ ascii: { fill: "#", empty: "-" }
2535
+ };
2536
+ var Gauge = ({
2537
+ value,
2538
+ min = 0,
2539
+ max = 100,
2540
+ width = 20,
2541
+ color,
2542
+ emptyColor,
2543
+ showPercent = true,
2544
+ variant = "unicode",
2545
+ fillChar,
2546
+ emptyChar,
2547
+ label
2548
+ }) => {
2549
+ const theme = useTheme();
2550
+ const effectiveColor = color ?? theme.semantic.success;
2551
+ const effectiveEmptyColor = emptyColor ?? theme.semantic.muted;
2552
+ const charSet = CHAR_SETS[variant];
2553
+ const effectiveFillChar = fillChar ?? charSet.fill;
2554
+ const effectiveEmptyChar = emptyChar ?? charSet.empty;
2555
+ const clampedValue = Math.min(Math.max(value, min), max);
2556
+ const range = max - min;
2557
+ const ratio = range === 0 ? 0 : (clampedValue - min) / range;
2558
+ const percent = Math.round(ratio * 100);
2559
+ const filledLength = Math.round(ratio * width);
2560
+ const emptyLength = width - filledLength;
2561
+ const filledStr = effectiveFillChar.repeat(filledLength);
2562
+ const emptyStr = effectiveEmptyChar.repeat(emptyLength);
2563
+ return /* @__PURE__ */ React6__default.default.createElement(ink.Box, { flexDirection: "row" }, label && /* @__PURE__ */ React6__default.default.createElement(ink.Box, { marginRight: 1 }, /* @__PURE__ */ React6__default.default.createElement(ink.Text, null, label)), /* @__PURE__ */ React6__default.default.createElement(ink.Text, { color: effectiveColor }, filledStr), /* @__PURE__ */ React6__default.default.createElement(ink.Text, { color: effectiveEmptyColor }, emptyStr), showPercent && /* @__PURE__ */ React6__default.default.createElement(ink.Box, { marginLeft: 1 }, /* @__PURE__ */ React6__default.default.createElement(ink.Text, null, percent, "%")));
2564
+ };
2565
+
2566
+ // src/components/BigNumber/font.ts
2567
+ var BLOCK_FONT = {
2568
+ "0": ["\u2588\u2580\u2588", "\u2588 \u2588", "\u2588\u2584\u2588"],
2569
+ "1": [" \u2588 ", " \u2588 ", " \u2588 "],
2570
+ "2": ["\u2580\u2580\u2588", " \u2580\u2584", "\u2588\u2584\u2584"],
2571
+ "3": ["\u2580\u2580\u2588", " \u2580\u2584", "\u2584\u2584\u2588"],
2572
+ "4": ["\u2588 \u2588", "\u2588\u2584\u2588", " \u2588"],
2573
+ "5": ["\u2588\u2580\u2580", "\u2580\u2580\u2584", "\u2584\u2584\u2588"],
2574
+ "6": ["\u2588\u2580\u2580", "\u2588\u2584\u2584", "\u2588\u2584\u2588"],
2575
+ "7": ["\u2580\u2580\u2588", " \u2588", " \u2588"],
2576
+ "8": ["\u2588\u2580\u2588", "\u2588\u2580\u2588", "\u2588\u2584\u2588"],
2577
+ "9": ["\u2588\u2580\u2588", "\u2580\u2580\u2588", " \u2588"],
2578
+ ".": [" ", " ", " \u2584 "],
2579
+ ",": [" ", " ", " \u2599 "],
2580
+ "%": ["\u2588 ", " \u2588 ", " \u2588"],
2581
+ "+": [" ", " \u253C ", " "],
2582
+ "-": [" ", " \u2500 ", " "]
2583
+ };
2584
+ var BRAILLE_FONT = {
2585
+ "0": ["\u28F0\u28C6", "\u2847\u28B8", "\u2819\u281B"],
2586
+ "1": ["\u2880\u2846", "\u2800\u2847", "\u2800\u2807"],
2587
+ "2": ["\u2824\u28E4", "\u2880\u2864", "\u2813\u2812"],
2588
+ "3": ["\u2824\u28E4", "\u2800\u2864", "\u2812\u281A"],
2589
+ "4": ["\u2846\u28B8", "\u2813\u28BA", "\u2800\u28B8"],
2590
+ "5": ["\u2816\u2836", "\u2812\u28B2", "\u2812\u281A"],
2591
+ "6": ["\u28B0\u2846", "\u2816\u28B2", "\u2813\u281A"],
2592
+ "7": ["\u2824\u28E4", "\u2800\u2870", "\u2800\u2847"],
2593
+ "8": ["\u28B0\u2846", "\u2816\u2876", "\u2813\u281A"],
2594
+ "9": ["\u28B0\u2846", "\u2813\u28BA", "\u2800\u28B8"],
2595
+ ".": ["\u2800\u2800", "\u2800\u2800", "\u2800\u2804"],
2596
+ ",": ["\u2800\u2800", "\u2800\u2800", "\u2800\u2822"],
2597
+ "%": ["\u2801\u2800", "\u2800\u2802", "\u2800\u2808"],
2598
+ "+": ["\u2800\u2800", "\u2810\u2812", "\u2800\u2800"],
2599
+ "-": ["\u2800\u2800", "\u2810\u2812", "\u2800\u2800"]
2600
+ };
2601
+ var ASCII_FONT = {
2602
+ "0": ["+~+", "| |", "+~+"],
2603
+ "1": [" | ", " | ", " | "],
2604
+ "2": ["~~+", "+-+", "+~~"],
2605
+ "3": ["~~+", " ~+", "~~+"],
2606
+ "4": ["+ +", "+-+", " +"],
2607
+ "5": ["+~~", "+~+", "~~+"],
2608
+ "6": ["+~~", "+~+", "+~+"],
2609
+ "7": ["~~+", " +", " +"],
2610
+ "8": ["+~+", "+~+", "+~+"],
2611
+ "9": ["+~+", "+~+", " +"],
2612
+ ".": [" ", " ", " . "],
2613
+ ",": [" ", " ", " , "],
2614
+ "%": ["* ", " * ", " *"],
2615
+ "+": [" ", " + ", " "],
2616
+ "-": [" ", " - ", " "]
2617
+ };
2618
+ var FONTS = {
2619
+ block: BLOCK_FONT,
2620
+ braille: BRAILLE_FONT,
2621
+ ascii: ASCII_FONT
2622
+ };
2623
+ var UNKNOWN = {
2624
+ block: [" ", " ? ", " "],
2625
+ braille: ["\u2800\u2800", "\u2800\u2826", "\u2800\u2800"],
2626
+ ascii: [" ", " ? ", " "]
2627
+ };
2628
+ function getBigChar(char, style = "block") {
2629
+ return FONTS[style][char] || UNKNOWN[style];
2630
+ }
2631
+ function renderBigString(text, style = "block") {
2632
+ const rows = ["", "", ""];
2633
+ for (const char of text) {
2634
+ const matrix = getBigChar(char, style);
2635
+ rows[0] += `${matrix[0]} `;
2636
+ rows[1] += `${matrix[1]} `;
2637
+ rows[2] += `${matrix[2]} `;
2638
+ }
2639
+ return rows;
2640
+ }
2641
+
2642
+ // src/components/BigNumber.tsx
2643
+ var TREND_ARROWS = {
2644
+ unicode: { up: "\u25B2", down: "\u25BC", neutral: "\u2500" },
2645
+ ascii: { up: "^", down: "v", neutral: "-" }
2646
+ };
2647
+ var BigNumber = ({
2648
+ value,
2649
+ label,
2650
+ color = "white",
2651
+ trendDirection,
2652
+ trendLabel,
2653
+ variant = "unicode",
2654
+ fontStyle = "block",
2655
+ align = "center"
2656
+ }) => {
2657
+ const bigLines = React6.useMemo(() => renderBigString(String(value), fontStyle), [value, fontStyle]);
2658
+ const theme = useTheme();
2659
+ const arrows = TREND_ARROWS[variant];
2660
+ let trendColor = theme.semantic.muted;
2661
+ let trendArrow = "";
2662
+ if (trendDirection === "up") {
2663
+ trendColor = theme.semantic.success;
2664
+ trendArrow = arrows.up;
2665
+ } else if (trendDirection === "down") {
2666
+ trendColor = theme.semantic.error;
2667
+ trendArrow = arrows.down;
2668
+ } else if (trendDirection === "neutral") {
2669
+ trendArrow = arrows.neutral;
2670
+ }
2671
+ const alignItems = align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start";
2672
+ return /* @__PURE__ */ React6__default.default.createElement(ink.Box, { flexDirection: "column", alignItems }, /* @__PURE__ */ React6__default.default.createElement(ink.Box, { flexDirection: "column", marginBottom: 1 }, bigLines.map((line, i) => /* @__PURE__ */ React6__default.default.createElement(ink.Text, { key: i, color }, line))), /* @__PURE__ */ React6__default.default.createElement(ink.Box, { flexDirection: "row", gap: 1 }, label && /* @__PURE__ */ React6__default.default.createElement(ink.Text, { color: theme.semantic.textSecondary }, label), (trendLabel || trendArrow) && /* @__PURE__ */ React6__default.default.createElement(ink.Text, { color: trendColor }, trendArrow, " ", trendLabel)));
2673
+ };
2674
+ var CHAR_SETS2 = {
2675
+ unicode: "\u25A0",
2676
+ ascii: "#"
2677
+ };
2678
+ var findMinMax = (data) => {
2679
+ let minVal = Number.POSITIVE_INFINITY;
2680
+ let maxVal = Number.NEGATIVE_INFINITY;
2681
+ for (const row of data) {
2682
+ for (const val of row) {
2683
+ if (val < minVal) minVal = val;
2684
+ if (val > maxVal) maxVal = val;
2685
+ }
2686
+ }
2687
+ return { min: minVal, max: maxVal };
2688
+ };
2689
+ var Heatmap = ({ data, colors, variant = "unicode", char }) => {
2690
+ const theme = useTheme();
2691
+ const effectiveColors = colors ?? theme.heatmapGradient;
2692
+ const effectiveChar = char ?? CHAR_SETS2[variant];
2693
+ const { min, max } = React6.useMemo(() => {
2694
+ const { min: minVal, max: maxVal } = findMinMax(data);
2695
+ if (minVal === Number.POSITIVE_INFINITY) return { min: 0, max: 0 };
2696
+ return { min: minVal, max: maxVal > minVal ? maxVal : minVal + 1 };
2697
+ }, [data]);
2698
+ const steps = effectiveColors.length;
2699
+ const gradient = React6.useMemo(
2700
+ () => createGradient(effectiveColors, steps),
2701
+ [effectiveColors, steps]
2702
+ );
2703
+ return /* @__PURE__ */ React6__default.default.createElement(ink.Box, { flexDirection: "column" }, data.map((row, rowIndex) => /* @__PURE__ */ React6__default.default.createElement(ink.Box, { key: rowIndex, flexDirection: "row" }, row.map((val, colIndex) => {
2704
+ const normalized = max === min ? 0 : (val - min) / (max - min);
2705
+ let stepIndex = Math.floor(normalized * steps);
2706
+ if (stepIndex >= steps) stepIndex = steps - 1;
2707
+ const colorFn = gradient[stepIndex];
2708
+ const renderedChar = colorFn ? colorFn(effectiveChar) : effectiveChar;
2709
+ return /* @__PURE__ */ React6__default.default.createElement(ink.Text, { key: `${rowIndex}-${colIndex}` }, renderedChar, " ");
2710
+ }))));
2711
+ };
2712
+ function parseLogLine(line) {
2713
+ const timeRegex = /\[?(\d{2,4}-\d{2}-\d{2}\s)?(\d{2}:\d{2}:\d{2})\]?/;
2714
+ const timeMatch = line.match(timeRegex);
2715
+ let timestamp = timeMatch ? timeMatch[0] : void 0;
2716
+ let content = line;
2717
+ if (timestamp) {
2718
+ content = content.replace(timestamp, "").trim();
2719
+ timestamp = timestamp.replace(/^\[|\]$/g, "");
2720
+ }
2721
+ const levelRegex = /(info|warn|warning|error|err|success|debug)/i;
2722
+ const levelMatch = content.match(levelRegex);
2723
+ let level = "unknown";
2724
+ if (levelMatch) {
2725
+ const lvl = levelMatch[0].toLowerCase();
2726
+ if (lvl.includes("error") || lvl.includes("err")) level = "error";
2727
+ else if (lvl.includes("warn")) level = "warn";
2728
+ else if (lvl.includes("info")) level = "info";
2729
+ else if (lvl.includes("success")) level = "success";
2730
+ else if (lvl.includes("debug")) level = "debug";
2731
+ content = content.replace(new RegExp(`\\[?${levelMatch[0]}\\]?`, "i"), "").trim();
2732
+ content = content.replace(/^[:\-\s]+/, "");
2733
+ }
2734
+ return {
2735
+ ...timestamp ? { timestamp } : {},
2736
+ level,
2737
+ message: content || line,
2738
+ raw: line
2739
+ };
2740
+ }
2741
+ var LogLine = ({ parsed, semantic }) => {
2742
+ const { timestamp, level, message } = parsed;
2743
+ const getStyle = (lvl) => {
2744
+ switch (lvl) {
2745
+ case "error":
2746
+ return { color: semantic.error, badge: "\u2716 ERROR", icon: "\u2716" };
2747
+ case "warn":
2748
+ return { color: semantic.warning, badge: "\u26A0 WARN ", icon: "\u26A0" };
2749
+ case "success":
2750
+ return { color: semantic.success, badge: "\u2714 SUCCESS", icon: "\u2714" };
2751
+ case "debug":
2752
+ return { color: semantic.muted, badge: "\u2699 DEBUG", icon: "\u2699" };
2753
+ default:
2754
+ return { color: semantic.info, badge: "\u2139 INFO ", icon: "\u2139" };
2755
+ }
2756
+ };
2757
+ const style = getStyle(level);
2758
+ return /* @__PURE__ */ React6__default.default.createElement(ink.Box, { flexDirection: "row", width: "100%" }, timestamp && /* @__PURE__ */ React6__default.default.createElement(ink.Box, { marginRight: 1, width: 10 }, /* @__PURE__ */ React6__default.default.createElement(ink.Text, { dimColor: true, wrap: "truncate" }, timestamp)), /* @__PURE__ */ React6__default.default.createElement(ink.Box, { marginRight: 1, width: 9 }, level === "unknown" ? /* @__PURE__ */ React6__default.default.createElement(ink.Text, { color: semantic.muted }, "\u2022") : /* @__PURE__ */ React6__default.default.createElement(ink.Text, { color: style.color, bold: true }, level === "error" || level === "warn" ? `${style.icon} ${level.toUpperCase()}` : level.toUpperCase())), /* @__PURE__ */ React6__default.default.createElement(ink.Box, { flexGrow: 1 }, level === "error" ? /* @__PURE__ */ React6__default.default.createElement(ink.Text, { color: style.color, wrap: "truncate-end" }, message) : /* @__PURE__ */ React6__default.default.createElement(ink.Text, { color: semantic.text, wrap: "truncate-end" }, message)));
2759
+ };
2760
+ var LogStream = ({ logs, maxLines = 100, height, width }) => {
2761
+ const theme = useTheme();
2762
+ const gridContext = React6.useContext(GridItemContext);
2763
+ const effectiveHeight = height ?? (typeof gridContext?.height === "number" ? gridContext.height : void 0);
2764
+ const effectiveWidth = width ?? gridContext?.width;
2765
+ const recentLogs = React6.useMemo(() => {
2766
+ const start = Math.max(0, logs.length - maxLines);
2767
+ return logs.slice(start);
2768
+ }, [logs, maxLines]);
2769
+ const displayLogs = React6.useMemo(() => {
2770
+ if (effectiveHeight && effectiveHeight > 0) {
2771
+ const start = Math.max(0, recentLogs.length - effectiveHeight);
2772
+ return recentLogs.slice(start);
2773
+ }
2774
+ return recentLogs;
2775
+ }, [recentLogs, effectiveHeight]);
2776
+ const items = displayLogs.map((line, index) => {
2777
+ const parsed = parseLogLine(line);
2778
+ return /* @__PURE__ */ React6__default.default.createElement(LogLine, { key: index, parsed, semantic: theme.semantic });
2779
+ });
2780
+ return /* @__PURE__ */ React6__default.default.createElement(
2781
+ ink.Box,
2782
+ {
2783
+ flexDirection: "column",
2784
+ justifyContent: "flex-end",
2785
+ flexGrow: 1,
2786
+ ...effectiveHeight !== void 0 && { height: effectiveHeight },
2787
+ ...effectiveWidth !== void 0 && { width: effectiveWidth }
2788
+ },
2789
+ items
2790
+ );
2791
+ };
2792
+ var ALIGN_MAP = {
2793
+ left: "flex-start",
2794
+ right: "flex-end",
2795
+ center: "center"
2796
+ };
2797
+ var SortableHeaderCell = ({
2798
+ column,
2799
+ width,
2800
+ isSorted,
2801
+ sortDirection,
2802
+ onSort,
2803
+ align,
2804
+ autoFocus
2805
+ }) => {
2806
+ const theme = useTheme();
2807
+ const semantic = theme.semantic;
2808
+ const { isFocused } = ink.useFocus({ autoFocus: !!autoFocus });
2809
+ ink.useInput((input, key) => {
2810
+ if (isFocused && (key.return || input === " ")) {
2811
+ onSort?.(column);
2812
+ }
2813
+ });
2814
+ let indicator = "";
2815
+ if (isSorted) {
2816
+ indicator = sortDirection === "asc" ? " \u25B2" : " \u25BC";
2817
+ }
2818
+ return /* @__PURE__ */ React6__default.default.createElement(
2819
+ ink.Box,
2820
+ {
2821
+ width,
2822
+ justifyContent: align,
2823
+ flexShrink: 0,
2824
+ paddingX: 1,
2825
+ ...isFocused ? { borderStyle: "single" } : {},
2826
+ borderColor: semantic.info,
2827
+ marginTop: isFocused ? -1 : 0
2828
+ },
2829
+ /* @__PURE__ */ React6__default.default.createElement(
2830
+ ink.Text,
2831
+ {
2832
+ bold: true,
2833
+ color: isFocused ? semantic.info : semantic.success,
2834
+ underline: isFocused,
2835
+ wrap: "truncate-end"
2836
+ },
2837
+ column.header,
2838
+ indicator
2839
+ )
2840
+ );
2841
+ };
2842
+ var getCellContentLength = (item, col) => {
2843
+ let content = "";
2844
+ if (typeof col.accessor === "function") {
2845
+ const result = col.accessor(item);
2846
+ if (typeof result === "string" || typeof result === "number") {
2847
+ content = String(result);
2848
+ }
2849
+ } else {
2850
+ const val = item[col.accessor];
2851
+ if (val !== void 0 && val !== null) {
2852
+ content = String(val);
2853
+ }
2854
+ }
2855
+ return content.length;
2856
+ };
2857
+ var calculateWidths = (columns, data) => {
2858
+ return columns.map((col) => {
2859
+ if (col.width) return col.width;
2860
+ let max = col.header.length + 2;
2861
+ for (const item of data) {
2862
+ max = Math.max(max, getCellContentLength(item, col));
2863
+ }
2864
+ return max + 2;
2865
+ });
2866
+ };
2867
+ var Table = ({
2868
+ data,
2869
+ columns,
2870
+ sortColumn,
2871
+ sortDirection = "asc",
2872
+ zebra = false,
2873
+ onSort
2874
+ }) => {
2875
+ const gridContext = React6.useContext(GridItemContext);
2876
+ const availableWidth = gridContext?.width;
2877
+ const contentWidths = React6.useMemo(() => calculateWidths(columns, data), [data, columns]);
2878
+ const totalContentWidth = contentWidths.reduce((a, b) => a + b, 0);
2879
+ const finalColumnWidths = React6.useMemo(() => {
2880
+ if (!availableWidth) {
2881
+ return contentWidths;
2882
+ }
2883
+ const scale = availableWidth / totalContentWidth;
2884
+ let allocated = 0;
2885
+ return contentWidths.map((w, i) => {
2886
+ if (i === contentWidths.length - 1) {
2887
+ return Math.max(1, availableWidth - allocated);
2888
+ }
2889
+ const newW = Math.floor(w * scale);
2890
+ allocated += newW;
2891
+ return Math.max(1, newW);
2892
+ });
2893
+ }, [availableWidth, totalContentWidth, contentWidths]);
2894
+ return /* @__PURE__ */ React6__default.default.createElement(
2895
+ ink.Box,
2896
+ {
2897
+ flexDirection: "column",
2898
+ ...availableWidth !== void 0 && { width: availableWidth }
2899
+ },
2900
+ /* @__PURE__ */ React6__default.default.createElement(
2901
+ ink.Box,
2902
+ {
2903
+ borderStyle: "single",
2904
+ borderTop: false,
2905
+ borderLeft: false,
2906
+ borderRight: false,
2907
+ borderBottom: true,
2908
+ flexDirection: "row"
2909
+ },
2910
+ columns.map((col, i) => {
2911
+ const width = finalColumnWidths[i] ?? 0;
2912
+ const isSorted = sortColumn === i || sortColumn === col.header;
2913
+ const justifyContent = col.align ? ALIGN_MAP[col.align] : "flex-start";
2914
+ return /* @__PURE__ */ React6__default.default.createElement(
2915
+ SortableHeaderCell,
2916
+ {
2917
+ key: i,
2918
+ column: col,
2919
+ width,
2920
+ isSorted,
2921
+ sortDirection,
2922
+ align: justifyContent,
2923
+ onSort: () => onSort?.(col, i),
2924
+ autoFocus: i === 0
2925
+ }
2926
+ );
2927
+ })
2928
+ ),
2929
+ data.map((item, rowIndex) => {
2930
+ const isZebra = zebra && rowIndex % 2 === 1;
2931
+ return /* @__PURE__ */ React6__default.default.createElement(ink.Box, { key: rowIndex, flexDirection: "row" }, columns.map((col, colIndex) => {
2932
+ const width = finalColumnWidths[colIndex] ?? 0;
2933
+ let content;
2934
+ if (typeof col.accessor === "function") {
2935
+ content = col.accessor(item);
2936
+ } else {
2937
+ const val = item[col.accessor];
2938
+ content = val !== void 0 && val !== null ? String(val) : "";
2939
+ }
2940
+ const justifyContent = col.align ? ALIGN_MAP[col.align] : "flex-start";
2941
+ return /* @__PURE__ */ React6__default.default.createElement(
2942
+ ink.Box,
2943
+ {
2944
+ key: colIndex,
2945
+ width,
2946
+ justifyContent,
2947
+ flexShrink: 0,
2948
+ paddingX: 1
2949
+ },
2950
+ /* @__PURE__ */ React6__default.default.createElement(ink.Text, { dimColor: isZebra, wrap: "truncate-end" }, content)
2951
+ );
2952
+ }));
2953
+ })
2954
+ );
2955
+ };
2956
+
2957
+ exports.AreaChart = AreaChart;
2958
+ exports.AsciiRenderer = AsciiRenderer;
2959
+ exports.Axis = Axis;
2960
+ exports.BarChart = BarChart;
2961
+ exports.BigNumber = BigNumber;
2962
+ exports.BlockRenderer = BlockRenderer;
2963
+ exports.BrailleRenderer = BrailleRenderer;
2964
+ exports.Gauge = Gauge;
2965
+ exports.Grid = Grid;
2966
+ exports.GridItem = GridItem;
2967
+ exports.Heatmap = Heatmap;
2968
+ exports.InkHudProvider = InkHudProvider;
2969
+ exports.Legend = Legend;
2970
+ exports.LineChart = LineChart;
2971
+ exports.LogStream = LogStream;
2972
+ exports.ONE_DARK_THEME = ONE_DARK_THEME;
2973
+ exports.Panel = Panel;
2974
+ exports.PieChart = PieChart;
2975
+ exports.Renderer = Renderer;
2976
+ exports.RendererSelector = RendererSelector;
2977
+ exports.Sparkline = Sparkline;
2978
+ exports.Table = Table;
2979
+ exports.TerminalDetector = TerminalDetector;
2980
+ exports.ThemeProvider = ThemeProvider;
2981
+ exports.arcPoints = arcPoints;
2982
+ exports.assignColors = assignColors;
2983
+ exports.averageDownsampling = averageDownsampling;
2984
+ exports.clamp = clamp;
2985
+ exports.colorToChalk = colorToChalk;
2986
+ exports.createGradient = createGradient;
2987
+ exports.degreesToRadians = degreesToRadians;
2988
+ exports.distanceBetweenPoints = distanceBetweenPoints;
2989
+ exports.easeInCubic = easeInCubic;
2990
+ exports.easeInOutQuad = easeInOutQuad;
2991
+ exports.easeLinear = easeLinear;
2992
+ exports.easeOutCubic = easeOutCubic;
2993
+ exports.fixedIntervalDownsampling = fixedIntervalDownsampling;
2994
+ exports.linearScale = linearScale;
2995
+ exports.lttb = lttb;
2996
+ exports.midpointCircle = midpointCircle;
2997
+ exports.minMaxDownsampling = minMaxDownsampling;
2998
+ exports.normalize = normalize;
2999
+ exports.pointOnArc = pointOnArc;
3000
+ exports.radiansToDegrees = radiansToDegrees;
3001
+ exports.rendererSelector = rendererSelector;
3002
+ exports.scaleToRange = scaleToRange;
3003
+ exports.terminalDetector = terminalDetector;
3004
+ exports.useInkHud = useInkHud;
3005
+ exports.useRendererSelector = useRendererSelector;
3006
+ exports.useSemanticColors = useSemanticColors;
3007
+ exports.useSmooth = useSmooth;
3008
+ exports.useSmoothArray = useSmoothArray;
3009
+ exports.useTheme = useTheme;
3010
+ exports.useThrottle = useThrottle;
3011
+ //# sourceMappingURL=index.cjs.map
3012
+ //# sourceMappingURL=index.cjs.map