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