linecraft 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.
Files changed (61) hide show
  1. package/.cursor/plan.md +952 -0
  2. package/LICENSE +22 -0
  3. package/README.md +111 -0
  4. package/TESTING.md +102 -0
  5. package/build.zig +100 -0
  6. package/examples/basic-progress.ts +21 -0
  7. package/examples/multi-lane.ts +29 -0
  8. package/examples/spinner.ts +20 -0
  9. package/examples/test-basic.ts +23 -0
  10. package/lib/components/progress-bar.d.ts +19 -0
  11. package/lib/components/progress-bar.d.ts.map +1 -0
  12. package/lib/components/progress-bar.js +43 -0
  13. package/lib/components/progress-bar.js.map +1 -0
  14. package/lib/components/spinner.d.ts +18 -0
  15. package/lib/components/spinner.d.ts.map +1 -0
  16. package/lib/components/spinner.js +48 -0
  17. package/lib/components/spinner.js.map +1 -0
  18. package/lib/index.d.ts +12 -0
  19. package/lib/index.d.ts.map +1 -0
  20. package/lib/index.js +16 -0
  21. package/lib/index.js.map +1 -0
  22. package/lib/native.d.ts +12 -0
  23. package/lib/native.d.ts.map +1 -0
  24. package/lib/native.js +65 -0
  25. package/lib/native.js.map +1 -0
  26. package/lib/region.d.ts +17 -0
  27. package/lib/region.d.ts.map +1 -0
  28. package/lib/region.js +74 -0
  29. package/lib/region.js.map +1 -0
  30. package/lib/types.d.ts +32 -0
  31. package/lib/types.d.ts.map +1 -0
  32. package/lib/types.js +2 -0
  33. package/lib/types.js.map +1 -0
  34. package/lib/utils/colors.d.ts +3 -0
  35. package/lib/utils/colors.d.ts.map +1 -0
  36. package/lib/utils/colors.js +61 -0
  37. package/lib/utils/colors.js.map +1 -0
  38. package/package.json +46 -0
  39. package/src/ts/components/progress-bar.ts +53 -0
  40. package/src/ts/components/spinner.ts +56 -0
  41. package/src/ts/index.ts +37 -0
  42. package/src/ts/native.ts +86 -0
  43. package/src/ts/region.ts +89 -0
  44. package/src/ts/types/ffi-napi.d.ts +11 -0
  45. package/src/ts/types/ref-napi.d.ts +5 -0
  46. package/src/ts/types.ts +53 -0
  47. package/src/ts/utils/colors.ts +72 -0
  48. package/src/zig/ansi.zig +21 -0
  49. package/src/zig/buffer.zig +37 -0
  50. package/src/zig/diff.zig +43 -0
  51. package/src/zig/region.zig +292 -0
  52. package/src/zig/renderer.zig +92 -0
  53. package/src/zig/test_ansi.zig +66 -0
  54. package/src/zig/test_buffer.zig +82 -0
  55. package/src/zig/test_diff.zig +220 -0
  56. package/src/zig/test_integration.zig +76 -0
  57. package/src/zig/test_region.zig +191 -0
  58. package/src/zig/test_runner.zig +27 -0
  59. package/src/zig/test_throttle.zig +59 -0
  60. package/src/zig/throttle.zig +38 -0
  61. package/tsconfig.json +21 -0
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 EchoKit Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # EchoKit
2
+
3
+ High-performance terminal UI library for Node.js with Zig backend.
4
+
5
+ EchoKit provides a friendly TypeScript API while leveraging Zig's speed and efficiency for terminal operations like diffing, ANSI cursor movement, buffering, and throttling.
6
+
7
+ ## Features
8
+
9
+ - **High Performance**: Zig backend handles all performance-critical operations
10
+ - **Automatic Batching**: Updates are automatically batched and throttled
11
+ - **Dynamic Regions**: Regions expand automatically as you add lines
12
+ - **Simple API**: Clean, intuitive TypeScript interface
13
+ - **Components**: Built-in progress bars and spinners
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pnpm install echokit
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### Basic Progress Bar
24
+
25
+ ```typescript
26
+ import { createRegion, createProgressBar } from 'echokit';
27
+
28
+ const region = createRegion({ width: 80 });
29
+ const progress = createProgressBar(region, 1, {
30
+ label: 'Installing packages',
31
+ width: 50,
32
+ });
33
+
34
+ for (let i = 0; i <= 100; i++) {
35
+ progress.update(i, 100);
36
+ await new Promise(resolve => setTimeout(resolve, 50));
37
+ }
38
+
39
+ progress.finish();
40
+ region.destroy();
41
+ ```
42
+
43
+ ### Spinner
44
+
45
+ ```typescript
46
+ import { createRegion, createSpinner } from 'echokit';
47
+
48
+ const region = createRegion({ width: 80 });
49
+ const spinner = createSpinner(region, 1);
50
+
51
+ spinner.setText('Processing...');
52
+ spinner.start();
53
+
54
+ // Do work...
55
+ await doWork();
56
+
57
+ spinner.stop();
58
+ region.destroy();
59
+ ```
60
+
61
+ ### Custom Layout
62
+
63
+ ```typescript
64
+ import { createRegion } from 'echokit';
65
+
66
+ const region = createRegion({ width: 80 });
67
+
68
+ // Set individual lines (1-based)
69
+ region.setLine(1, { text: 'Status:', style: { bold: true } });
70
+ region.setLine(2, ' ✓ Connected');
71
+ region.setLine(3, ' ⏳ Processing...');
72
+
73
+ // Or set entire content at once
74
+ region.set(`Status:
75
+ ✓ Connected
76
+ ⏳ Processing...
77
+ ✗ Error`);
78
+ ```
79
+
80
+ ## API
81
+
82
+ ### `createRegion(options?)`
83
+
84
+ Creates a new terminal region.
85
+
86
+ **Options:**
87
+ - `x?: number` - X position (default: 0)
88
+ - `y?: number` - Y position (default: 0)
89
+ - `width?: number` - Width (default: terminal width)
90
+ - `height?: number` - Initial height (default: 1, expands as needed)
91
+
92
+ ### `region.setLine(lineNumber, content)`
93
+
94
+ Set a single line (1-based). Region expands automatically if needed.
95
+
96
+ ### `region.set(content)`
97
+
98
+ Set entire content. Can be a string with `\n` separators or an array of `LineContent`.
99
+
100
+ ### `region.flush()`
101
+
102
+ Force immediate render of pending updates (bypasses throttle).
103
+
104
+ ### `region.setThrottle(fps)`
105
+
106
+ Set maximum render rate (default: 60 FPS).
107
+
108
+ ## License
109
+
110
+ MIT
111
+
package/TESTING.md ADDED
@@ -0,0 +1,102 @@
1
+ # Testing EchoKit
2
+
3
+ ## Running Tests
4
+
5
+ ### All Tests
6
+ ```bash
7
+ zig build test
8
+ # or
9
+ pnpm test
10
+ ```
11
+
12
+ ### Individual Test Modules
13
+ ```bash
14
+ # ANSI tests only
15
+ zig build test:ansi
16
+
17
+ # Throttle tests only
18
+ zig build test:throttle
19
+
20
+ # Buffer tests only
21
+ zig build test:buffer
22
+
23
+ # Diff tests only
24
+ zig build test:diff
25
+
26
+ # Region tests only
27
+ zig build test:region
28
+ ```
29
+
30
+ ## Test Coverage
31
+
32
+ ### ANSI Module (`test_ansi.zig`)
33
+ - ✅ `move_cursor_to` - Generates correct cursor positioning ANSI
34
+ - ✅ `move_cursor_up` - Generates correct up movement ANSI
35
+ - ✅ `move_cursor_down` - Generates correct down movement ANSI
36
+ - ✅ ANSI constants - All constants verified
37
+ - ✅ `move_cursor_to` with zero coordinates
38
+ - ✅ `move_cursor_to` with large coordinates
39
+ - ✅ `move_cursor_up` with zero
40
+
41
+ ### Throttle Module (`test_throttle.zig`)
42
+ - ✅ Initialization - Creates throttle with correct FPS
43
+ - ✅ `set_fps` - Changes FPS correctly
44
+ - ✅ `should_render` - Allows first frame
45
+ - ✅ `should_render` - Respects interval (blocks immediate second frame)
46
+ - ✅ `time_until_next_frame` - Calculates remaining time correctly
47
+ - ✅ Very high FPS (1000 FPS)
48
+ - ✅ Very low FPS (1 FPS)
49
+
50
+ ### Buffer Module (`test_buffer.zig`)
51
+ - ✅ Initialization - Creates empty buffer
52
+ - ✅ `write` - Appends data correctly
53
+ - ✅ `clear` - Clears buffer
54
+ - ✅ `flush` - Writes to stdout and clears
55
+ - ✅ Large content handling
56
+ - ✅ Multiple writes and flush
57
+
58
+ ### Diff Module (`test_diff.zig`)
59
+ - ✅ Identical frames - Returns no_change ops
60
+ - ✅ Changed line - Detects line updates
61
+ - ✅ Inserted line - Detects new lines
62
+ - ✅ Deleted line - Detects removed lines
63
+ - ✅ Multiple changes - Handles complex diffs
64
+ - ✅ Empty frames
65
+ - ✅ Completely different frames
66
+
67
+ ### Region Module (`test_region.zig`)
68
+ - ✅ Initialization - Creates region with correct dimensions
69
+ - ✅ `set_line` - Expands automatically when needed
70
+ - ✅ `set_line` - Rejects line 0 (1-based indexing)
71
+ - ✅ `set` - Splits content by newlines correctly
72
+ - ✅ `expand_to` - Expands region height
73
+ - ✅ `clear_line` - Clears individual lines
74
+ - ✅ Throttle configuration - Sets FPS correctly
75
+ - ✅ `set_line` with empty string
76
+ - ✅ `set` with empty content
77
+ - ✅ `set` with single line (no newline)
78
+ - ✅ Multiple `set_line` calls
79
+ - ✅ `clear` all lines
80
+
81
+ ### Integration Tests (`test_integration.zig`)
82
+ - ✅ Region with diff and throttle together
83
+ - ✅ Region expansion with multiple operations
84
+ - ✅ `set` then `set_line` interaction
85
+
86
+ ## Test Output
87
+
88
+ All tests print visible output to the console using `std.debug.print()`. Each test shows:
89
+ - ✓ Checkmark for passed assertions
90
+ - Test name and module
91
+ - Relevant values being tested
92
+
93
+ Example output:
94
+ ```
95
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
96
+ Testing: ANSI Module
97
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
98
+ ✓ move_cursor_to(10, 5): [1b[5;10H
99
+ ✓ move_cursor_up(3): [1b[3A
100
+ ✓ All ANSI constants verified
101
+ ```
102
+
package/build.zig ADDED
@@ -0,0 +1,100 @@
1
+ const std = @import("std");
2
+
3
+ pub fn build(b: *std.Build) void {
4
+ const target = b.standardTargetOptions(.{});
5
+ const optimize = b.standardOptimizeOption(.{});
6
+
7
+ // Create shared library for Node.js addon
8
+ // In Zig 0.15.x, target and optimize must be set in createModule
9
+ const lib = b.addLibrary(.{
10
+ .name = "echokit",
11
+ .root_module = b.createModule(.{
12
+ .root_source_file = b.path("src/zig/renderer.zig"),
13
+ .target = target,
14
+ .optimize = optimize,
15
+ }),
16
+ .linkage = .dynamic, // Shared library for Node.js addon
17
+ });
18
+
19
+ // Link with libc (needed for N-API)
20
+ lib.linkLibC();
21
+
22
+ // Install the library (defaults to zig-out/lib/)
23
+ b.installArtifact(lib);
24
+
25
+ // Test all modules together
26
+ // Using addTest automatically discovers all test functions
27
+ const test_exe = b.addTest(.{
28
+ .root_module = b.createModule(.{
29
+ .root_source_file = b.path("src/zig/test_runner.zig"),
30
+ .target = target,
31
+ .optimize = optimize,
32
+ }),
33
+ });
34
+
35
+ const run_tests = b.addRunArtifact(test_exe);
36
+ run_tests.has_side_effects = true;
37
+ const test_step = b.step("test", "Run all Zig tests with visible console output");
38
+ test_step.dependOn(&run_tests.step);
39
+
40
+ // Individual test modules (can be run separately for focused testing)
41
+ const test_ansi = b.addTest(.{
42
+ .root_module = b.createModule(.{
43
+ .root_source_file = b.path("src/zig/test_ansi.zig"),
44
+ .target = target,
45
+ .optimize = optimize,
46
+ }),
47
+ });
48
+ const run_test_ansi = b.addRunArtifact(test_ansi);
49
+ run_test_ansi.has_side_effects = true;
50
+ const test_ansi_step = b.step("test:ansi", "Run ANSI tests only");
51
+ test_ansi_step.dependOn(&run_test_ansi.step);
52
+
53
+ const test_throttle = b.addTest(.{
54
+ .root_module = b.createModule(.{
55
+ .root_source_file = b.path("src/zig/test_throttle.zig"),
56
+ .target = target,
57
+ .optimize = optimize,
58
+ }),
59
+ });
60
+ const run_test_throttle = b.addRunArtifact(test_throttle);
61
+ run_test_throttle.has_side_effects = true;
62
+ const test_throttle_step = b.step("test:throttle", "Run Throttle tests only");
63
+ test_throttle_step.dependOn(&run_test_throttle.step);
64
+
65
+ const test_buffer = b.addTest(.{
66
+ .root_module = b.createModule(.{
67
+ .root_source_file = b.path("src/zig/test_buffer.zig"),
68
+ .target = target,
69
+ .optimize = optimize,
70
+ }),
71
+ });
72
+ const run_test_buffer = b.addRunArtifact(test_buffer);
73
+ run_test_buffer.has_side_effects = true;
74
+ const test_buffer_step = b.step("test:buffer", "Run Buffer tests only");
75
+ test_buffer_step.dependOn(&run_test_buffer.step);
76
+
77
+ const test_diff = b.addTest(.{
78
+ .root_module = b.createModule(.{
79
+ .root_source_file = b.path("src/zig/test_diff.zig"),
80
+ .target = target,
81
+ .optimize = optimize,
82
+ }),
83
+ });
84
+ const run_test_diff = b.addRunArtifact(test_diff);
85
+ run_test_diff.has_side_effects = true;
86
+ const test_diff_step = b.step("test:diff", "Run Diff tests only");
87
+ test_diff_step.dependOn(&run_test_diff.step);
88
+
89
+ const test_region = b.addTest(.{
90
+ .root_module = b.createModule(.{
91
+ .root_source_file = b.path("src/zig/test_region.zig"),
92
+ .target = target,
93
+ .optimize = optimize,
94
+ }),
95
+ });
96
+ const run_test_region = b.addRunArtifact(test_region);
97
+ run_test_region.has_side_effects = true;
98
+ const test_region_step = b.step("test:region", "Run Region tests only");
99
+ test_region_step.dependOn(&run_test_region.step);
100
+ }
@@ -0,0 +1,21 @@
1
+ import { createRegion, createProgressBar } from '../src/ts';
2
+
3
+ async function main() {
4
+ const region = createRegion({ width: 80 });
5
+ const progress = createProgressBar(region, 1, {
6
+ label: 'Installing packages',
7
+ width: 50,
8
+ });
9
+
10
+ for (let i = 0; i <= 100; i++) {
11
+ progress.update(i, 100);
12
+ await new Promise(resolve => setTimeout(resolve, 50));
13
+ }
14
+
15
+ progress.finish();
16
+ await new Promise(resolve => setTimeout(resolve, 1000));
17
+ region.destroy();
18
+ }
19
+
20
+ main().catch(console.error);
21
+
@@ -0,0 +1,29 @@
1
+ import { createRegion, createProgressBar } from '../src/ts/index.js';
2
+
3
+ async function updateProgress(progress: ReturnType<typeof createProgressBar>, total: number) {
4
+ for (let i = 0; i <= total; i++) {
5
+ progress.update(i, total);
6
+ await new Promise(resolve => setTimeout(resolve, 20 + Math.random() * 30));
7
+ }
8
+ }
9
+
10
+ async function main() {
11
+ const region = createRegion({ width: 80 });
12
+
13
+ const download = createProgressBar(region, 1, { label: 'Downloading' });
14
+ const extract = createProgressBar(region, 2, { label: 'Extracting' });
15
+ const install = createProgressBar(region, 3, { label: 'Installing' });
16
+
17
+ // Update lanes concurrently
18
+ await Promise.all([
19
+ updateProgress(download, 100),
20
+ updateProgress(extract, 100),
21
+ updateProgress(install, 100),
22
+ ]);
23
+
24
+ await new Promise(resolve => setTimeout(resolve, 1000));
25
+ region.destroy();
26
+ }
27
+
28
+ main().catch(console.error);
29
+
@@ -0,0 +1,20 @@
1
+ import { createRegion, createSpinner } from '../src/ts/index.js';
2
+
3
+ async function main() {
4
+ const region = createRegion({ width: 80 });
5
+ const spinner = createSpinner(region, 1);
6
+
7
+ spinner.setText('Processing...');
8
+ spinner.start();
9
+
10
+ // Simulate work
11
+ await new Promise(resolve => setTimeout(resolve, 3000));
12
+
13
+ spinner.stop();
14
+ region.setLine(1, '✓ Done!');
15
+ await new Promise(resolve => setTimeout(resolve, 1000));
16
+ region.destroy();
17
+ }
18
+
19
+ main().catch(console.error);
20
+
@@ -0,0 +1,23 @@
1
+ // Simple test to verify the Node.js API works
2
+ import { createRegion } from '../src/ts/index.js';
3
+
4
+ console.log('Creating region...');
5
+ const region = createRegion({ x: 0, y: 0, width: 80, height: 1 });
6
+
7
+ console.log('Setting line 1...');
8
+ region.setLine(1, 'Hello from EchoKit!');
9
+
10
+ console.log('Setting line 2 (should expand)...');
11
+ region.setLine(2, 'This is line 2');
12
+
13
+ console.log('Setting multiple lines...');
14
+ region.set('Line 1\nLine 2\nLine 3');
15
+
16
+ console.log('Flushing...');
17
+ region.flush();
18
+
19
+ console.log('Cleaning up...');
20
+ region.destroy();
21
+
22
+ console.log('Test complete!');
23
+
@@ -0,0 +1,19 @@
1
+ import { TerminalRegion } from '../region.js';
2
+ import type { ProgressBarOptions } from '../types.js';
3
+ export declare class ProgressBar {
4
+ private region;
5
+ private lineNumber;
6
+ private current;
7
+ private total;
8
+ private label;
9
+ private width;
10
+ private completeChar;
11
+ private incompleteChar;
12
+ private brackets;
13
+ constructor(region: TerminalRegion, lineNumber: number, options?: ProgressBarOptions);
14
+ update(current: number, total: number): void;
15
+ setLabel(label: string): void;
16
+ private render;
17
+ finish(): void;
18
+ }
19
+ //# sourceMappingURL=progress-bar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress-bar.d.ts","sourceRoot":"","sources":["../../src/ts/components/progress-bar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAmB;gBAEvB,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB;IAUxF,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAM5C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK7B,OAAO,CAAC,MAAM;IAad,MAAM,IAAI,IAAI;CAGf"}
@@ -0,0 +1,43 @@
1
+ export class ProgressBar {
2
+ region;
3
+ lineNumber; // 1-based
4
+ current = 0;
5
+ total = 100;
6
+ label;
7
+ width;
8
+ completeChar;
9
+ incompleteChar;
10
+ brackets;
11
+ constructor(region, lineNumber, options = {}) {
12
+ this.region = region;
13
+ this.lineNumber = lineNumber;
14
+ this.label = options.label || '';
15
+ this.width = options.width ?? 40;
16
+ this.completeChar = options.style?.complete ?? '█';
17
+ this.incompleteChar = options.style?.incomplete ?? '░';
18
+ this.brackets = options.style?.brackets ?? ['[', ']'];
19
+ }
20
+ update(current, total) {
21
+ this.current = current;
22
+ this.total = total;
23
+ this.render();
24
+ }
25
+ setLabel(label) {
26
+ this.label = label;
27
+ this.render();
28
+ }
29
+ render() {
30
+ const percentage = Math.min(100, Math.max(0, (this.current / this.total) * 100));
31
+ const filled = Math.floor((percentage / 100) * this.width);
32
+ const empty = this.width - filled;
33
+ const bar = this.completeChar.repeat(filled) + this.incompleteChar.repeat(empty);
34
+ const text = `${this.label} ${this.brackets[0]}${bar}${this.brackets[1]} ${percentage.toFixed(1)}%`;
35
+ // Just update the line - Zig handles batching and rendering
36
+ this.region.setLine(this.lineNumber, text);
37
+ // Optional: call flush() if you need immediate rendering
38
+ }
39
+ finish() {
40
+ this.update(this.total, this.total);
41
+ }
42
+ }
43
+ //# sourceMappingURL=progress-bar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress-bar.js","sourceRoot":"","sources":["../../src/ts/components/progress-bar.ts"],"names":[],"mappings":"AAGA,MAAM,OAAO,WAAW;IACd,MAAM,CAAiB;IACvB,UAAU,CAAS,CAAC,UAAU;IAC9B,OAAO,GAAW,CAAC,CAAC;IACpB,KAAK,GAAW,GAAG,CAAC;IACpB,KAAK,CAAS;IACd,KAAK,CAAS;IACd,YAAY,CAAS;IACrB,cAAc,CAAS;IACvB,QAAQ,CAAmB;IAEnC,YAAY,MAAsB,EAAE,UAAkB,EAAE,UAA8B,EAAE;QACtF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,KAAK,EAAE,QAAQ,IAAI,GAAG,CAAC;QACnD,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,KAAK,EAAE,UAAU,IAAI,GAAG,CAAC;QACvD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,KAAK,EAAE,QAAQ,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,CAAC,OAAe,EAAE,KAAa;QACnC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAED,QAAQ,CAAC,KAAa;QACpB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAEO,MAAM;QACZ,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;QACjF,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,UAAU,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC;QAElC,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACjF,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;QAEpG,4DAA4D;QAC5D,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAC3C,yDAAyD;IAC3D,CAAC;IAED,MAAM;QACJ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;CACF"}
@@ -0,0 +1,18 @@
1
+ import { TerminalRegion } from '../region.js';
2
+ import type { SpinnerOptions } from '../types.js';
3
+ export declare class Spinner {
4
+ private region;
5
+ private lineNumber;
6
+ private frameIndex;
7
+ private text;
8
+ private interval?;
9
+ private isRunning;
10
+ private frames;
11
+ private intervalMs;
12
+ constructor(region: TerminalRegion, lineNumber: number, options?: SpinnerOptions);
13
+ start(): void;
14
+ stop(): void;
15
+ setText(text: string): void;
16
+ private render;
17
+ }
18
+ //# sourceMappingURL=spinner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../../src/ts/components/spinner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAIlD,qBAAa,OAAO;IAClB,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,QAAQ,CAAC,CAAiB;IAClC,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,UAAU,CAAS;gBAEf,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB;IAOpF,KAAK,IAAI,IAAI;IASb,IAAI,IAAI,IAAI;IAUZ,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAK3B,OAAO,CAAC,MAAM;CAQf"}
@@ -0,0 +1,48 @@
1
+ const DEFAULT_SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
2
+ export class Spinner {
3
+ region;
4
+ lineNumber; // 1-based
5
+ frameIndex = 0;
6
+ text = '';
7
+ interval;
8
+ isRunning = false;
9
+ frames;
10
+ intervalMs;
11
+ constructor(region, lineNumber, options = {}) {
12
+ this.region = region;
13
+ this.lineNumber = lineNumber;
14
+ this.frames = options.frames ?? DEFAULT_SPINNER_FRAMES;
15
+ this.intervalMs = options.interval ?? 100;
16
+ }
17
+ start() {
18
+ if (this.isRunning)
19
+ return;
20
+ this.isRunning = true;
21
+ this.interval = setInterval(() => {
22
+ this.render();
23
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
24
+ }, this.intervalMs);
25
+ }
26
+ stop() {
27
+ if (!this.isRunning)
28
+ return;
29
+ this.isRunning = false;
30
+ if (this.interval) {
31
+ clearInterval(this.interval);
32
+ }
33
+ // Clear the spinner line
34
+ this.region.setLine(this.lineNumber, '');
35
+ }
36
+ setText(text) {
37
+ this.text = text;
38
+ this.render();
39
+ }
40
+ render() {
41
+ const frame = this.frames[this.frameIndex];
42
+ const line = `${frame} ${this.text}`;
43
+ // Just update the line - Zig handles batching and rendering
44
+ this.region.setLine(this.lineNumber, line);
45
+ // Spinner updates frequently, so Zig's throttling will handle smooth animation
46
+ }
47
+ }
48
+ //# sourceMappingURL=spinner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spinner.js","sourceRoot":"","sources":["../../src/ts/components/spinner.ts"],"names":[],"mappings":"AAGA,MAAM,sBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAElF,MAAM,OAAO,OAAO;IACV,MAAM,CAAiB;IACvB,UAAU,CAAS,CAAC,UAAU;IAC9B,UAAU,GAAW,CAAC,CAAC;IACvB,IAAI,GAAW,EAAE,CAAC;IAClB,QAAQ,CAAkB;IAC1B,SAAS,GAAY,KAAK,CAAC;IAC3B,MAAM,CAAW;IACjB,UAAU,CAAS;IAE3B,YAAY,MAAsB,EAAE,UAAkB,EAAE,UAA0B,EAAE;QAClF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,sBAAsB,CAAC;QACvD,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,QAAQ,IAAI,GAAG,CAAC;IAC5C,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO;QAC3B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;YAC/B,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,UAAU,GAAG,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;QAC/D,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACtB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAC5B,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/B,CAAC;QACD,yBAAyB;QACzB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,OAAO,CAAC,IAAY;QAClB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAEO,MAAM;QACZ,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,GAAG,KAAK,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QAErC,4DAA4D;QAC5D,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAC3C,+EAA+E;IACjF,CAAC;CACF"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export { TerminalRegion } from './region.js';
2
+ export { ProgressBar } from './components/progress-bar.js';
3
+ export { Spinner } from './components/spinner.js';
4
+ export type { RegionOptions, LineContent, TextStyle, Color, ProgressBarOptions, SpinnerOptions, } from './types.js';
5
+ import { TerminalRegion } from './region.js';
6
+ import { ProgressBar } from './components/progress-bar.js';
7
+ import { Spinner } from './components/spinner.js';
8
+ import type { RegionOptions, ProgressBarOptions, SpinnerOptions } from './types.js';
9
+ export declare function createRegion(options?: RegionOptions): TerminalRegion;
10
+ export declare function createProgressBar(region: TerminalRegion, lineNumber: number, options?: ProgressBarOptions): ProgressBar;
11
+ export declare function createSpinner(region: TerminalRegion, lineNumber: number, options?: SpinnerOptions): Spinner;
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/ts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,YAAY,EACV,aAAa,EACb,WAAW,EACX,SAAS,EACT,KAAK,EACL,kBAAkB,EAClB,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEpF,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,cAAc,CAEpE;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,cAAc,EACtB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,WAAW,CAEb;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,cAAc,EACtB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAET"}
package/lib/index.js ADDED
@@ -0,0 +1,16 @@
1
+ export { TerminalRegion } from './region.js';
2
+ export { ProgressBar } from './components/progress-bar.js';
3
+ export { Spinner } from './components/spinner.js';
4
+ import { TerminalRegion } from './region.js';
5
+ import { ProgressBar } from './components/progress-bar.js';
6
+ import { Spinner } from './components/spinner.js';
7
+ export function createRegion(options) {
8
+ return new TerminalRegion(options);
9
+ }
10
+ export function createProgressBar(region, lineNumber, options) {
11
+ return new ProgressBar(region, lineNumber, options);
12
+ }
13
+ export function createSpinner(region, lineNumber, options) {
14
+ return new Spinner(region, lineNumber, options);
15
+ }
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/ts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAUlD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAGlD,MAAM,UAAU,YAAY,CAAC,OAAuB;IAClD,OAAO,IAAI,cAAc,CAAC,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,MAAsB,EACtB,UAAkB,EAClB,OAA4B;IAE5B,OAAO,IAAI,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,MAAsB,EACtB,UAAkB,EAClB,OAAwB;IAExB,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,12 @@
1
+ export interface NativeRegion {
2
+ createRegion(x: number, y: number, width: number, height: number): number;
3
+ destroyRegion(handle: number): void;
4
+ setLine(handle: number, lineNumber: number, content: string): void;
5
+ set(handle: number, content: string): void;
6
+ clearLine(handle: number, lineNumber: number): void;
7
+ clearRegion(handle: number): void;
8
+ flush(handle: number): void;
9
+ setThrottleFps(handle: number, fps: number): void;
10
+ }
11
+ export declare const native: NativeRegion;
12
+ //# sourceMappingURL=native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/ts/native.ts"],"names":[],"mappings":"AAiCA,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1E,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACnE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACpD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACnD;AAED,eAAO,MAAM,MAAM,EAAE,YAwCpB,CAAC"}