linecraft 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/LICENSE +0 -1
  2. package/README.md +3 -1
  3. package/lib/component.d.ts +34 -0
  4. package/lib/component.d.ts.map +1 -0
  5. package/lib/component.js +42 -0
  6. package/lib/component.js.map +1 -0
  7. package/lib/components/code-debug.d.ts +35 -0
  8. package/lib/components/code-debug.d.ts.map +1 -0
  9. package/lib/components/code-debug.js +294 -0
  10. package/lib/components/code-debug.js.map +1 -0
  11. package/lib/components/fill.d.ts +15 -0
  12. package/lib/components/fill.d.ts.map +1 -0
  13. package/lib/components/fill.js +37 -0
  14. package/lib/components/fill.js.map +1 -0
  15. package/lib/components/index.d.ts +2 -2
  16. package/lib/components/index.d.ts.map +1 -1
  17. package/lib/components/index.js +2 -2
  18. package/lib/components/index.js.map +1 -1
  19. package/lib/components/progress-bar-grid.d.ts +1 -1
  20. package/lib/components/progress-bar-grid.d.ts.map +1 -1
  21. package/lib/components/progress-bar-grid.js +6 -6
  22. package/lib/components/progress-bar-grid.js.map +1 -1
  23. package/lib/components/prompt.d.ts +4 -5
  24. package/lib/components/prompt.d.ts.map +1 -1
  25. package/lib/components/prompt.js +17 -69
  26. package/lib/components/prompt.js.map +1 -1
  27. package/lib/components/section.d.ts +33 -0
  28. package/lib/components/section.d.ts.map +1 -0
  29. package/lib/components/section.js +178 -0
  30. package/lib/components/section.js.map +1 -0
  31. package/lib/components/segments.d.ts +26 -0
  32. package/lib/components/segments.d.ts.map +1 -0
  33. package/lib/components/segments.js +105 -0
  34. package/lib/components/segments.js.map +1 -0
  35. package/lib/components/spinner.d.ts +18 -16
  36. package/lib/components/spinner.d.ts.map +1 -1
  37. package/lib/components/spinner.js +63 -47
  38. package/lib/components/spinner.js.map +1 -1
  39. package/lib/components/style.test.js +11 -11
  40. package/lib/components/style.test.js.map +1 -1
  41. package/lib/components/styled.d.ts +17 -0
  42. package/lib/components/styled.d.ts.map +1 -0
  43. package/lib/components/styled.js +107 -0
  44. package/lib/components/styled.js.map +1 -0
  45. package/lib/components/styled.test.d.ts +2 -0
  46. package/lib/components/styled.test.d.ts.map +1 -0
  47. package/lib/components/styled.test.js +135 -0
  48. package/lib/components/styled.test.js.map +1 -0
  49. package/lib/index.d.ts +17 -13
  50. package/lib/index.d.ts.map +1 -1
  51. package/lib/index.js +13 -13
  52. package/lib/index.js.map +1 -1
  53. package/lib/index.test.js +17 -11
  54. package/lib/index.test.js.map +1 -1
  55. package/lib/layout/grid.d.ts +31 -35
  56. package/lib/layout/grid.d.ts.map +1 -1
  57. package/lib/layout/grid.js +437 -216
  58. package/lib/layout/grid.js.map +1 -1
  59. package/lib/layout/grid.test.js +332 -36
  60. package/lib/layout/grid.test.js.map +1 -1
  61. package/lib/native/ansi.d.ts +9 -0
  62. package/lib/native/ansi.d.ts.map +1 -1
  63. package/lib/native/ansi.js +9 -0
  64. package/lib/native/ansi.js.map +1 -1
  65. package/lib/native/diff.d.ts +5 -1
  66. package/lib/native/diff.d.ts.map +1 -1
  67. package/lib/native/diff.js +25 -7
  68. package/lib/native/diff.js.map +1 -1
  69. package/lib/native/region-renderer-debug.test.d.ts +2 -0
  70. package/lib/native/region-renderer-debug.test.d.ts.map +1 -0
  71. package/lib/native/region-renderer-debug.test.js +45 -0
  72. package/lib/native/region-renderer-debug.test.js.map +1 -0
  73. package/lib/native/region-renderer.d.ts +57 -148
  74. package/lib/native/region-renderer.d.ts.map +1 -1
  75. package/lib/native/region-renderer.js +455 -1124
  76. package/lib/native/region-renderer.js.map +1 -1
  77. package/lib/native/region.test.js +2 -20
  78. package/lib/native/region.test.js.map +1 -1
  79. package/lib/region-resize.test.d.ts +2 -0
  80. package/lib/region-resize.test.d.ts.map +1 -0
  81. package/lib/region-resize.test.js +124 -0
  82. package/lib/region-resize.test.js.map +1 -0
  83. package/lib/region.d.ts +97 -9
  84. package/lib/region.d.ts.map +1 -1
  85. package/lib/region.js +591 -185
  86. package/lib/region.js.map +1 -1
  87. package/lib/region.test.js +3 -3
  88. package/lib/region.test.js.map +1 -1
  89. package/lib/types.d.ts +9 -0
  90. package/lib/types.d.ts.map +1 -1
  91. package/lib/utils/file-link.d.ts +16 -0
  92. package/lib/utils/file-link.d.ts.map +1 -0
  93. package/lib/utils/file-link.js +23 -0
  94. package/lib/utils/file-link.js.map +1 -0
  95. package/lib/utils/prompt.d.ts +15 -0
  96. package/lib/utils/prompt.d.ts.map +1 -0
  97. package/lib/utils/prompt.js +128 -0
  98. package/lib/utils/prompt.js.map +1 -0
  99. package/lib/utils/terminal-theme.d.ts +36 -0
  100. package/lib/utils/terminal-theme.d.ts.map +1 -0
  101. package/lib/utils/terminal-theme.js +61 -0
  102. package/lib/utils/terminal-theme.js.map +1 -0
  103. package/lib/utils/text.d.ts +53 -3
  104. package/lib/utils/text.d.ts.map +1 -1
  105. package/lib/utils/text.js +194 -36
  106. package/lib/utils/text.js.map +1 -1
  107. package/lib/utils/wait-for-spacebar.d.ts.map +1 -1
  108. package/lib/utils/wait-for-spacebar.js +9 -6
  109. package/lib/utils/wait-for-spacebar.js.map +1 -1
  110. package/package.json +13 -13
@@ -1,77 +1,25 @@
1
1
  // Prompt component - improved API for prompts like "Press SPACEBAR"
2
- import { color } from '../api/color';
2
+ import { Styled } from './styled';
3
3
  /**
4
- * Show a prompt message (like "Press SPACEBAR to continue...")
5
- * This is a better API than waitForSpacebar for general prompts
4
+ * Create a Prompt component that can be added to a region
5
+ * The component renders a blank line followed by the prompt message
6
6
  */
7
- export async function showPrompt(region, options) {
8
- const { message, key = 'SPACEBAR', color: promptColor = 'brightBlack', position = 'below', } = options;
9
- const currentHeight = region.height;
10
- const promptText = color(promptColor, `Press ${key} to ${message}...`);
11
- if (position === 'below') {
12
- // Add blank line, then prompt
13
- region.setLine(currentHeight + 1, '');
14
- region.setLine(currentHeight + 2, promptText);
15
- }
16
- else {
17
- // Add prompt above current content
18
- // This would require shifting all content down, which is complex
19
- // For now, just add below
20
- region.setLine(currentHeight + 1, '');
21
- region.setLine(currentHeight + 2, promptText);
22
- }
23
- region.flush();
24
- // Wait for keypress
25
- return new Promise((resolve) => {
26
- const stdin = process.stdin;
27
- if (!stdin.isTTY) {
28
- resolve();
29
- return;
7
+ export function Prompt(options) {
8
+ const { message, key = 'SPACEBAR', color: promptColor = 'brightBlack', } = options;
9
+ return (ctx) => {
10
+ const promptText = `Press ${key} to ${message}...`;
11
+ const styledComponent = Styled({ color: promptColor }, promptText);
12
+ const styledResult = styledComponent(ctx);
13
+ // Return blank line + prompt
14
+ if (typeof styledResult === 'string') {
15
+ return ['', styledResult];
30
16
  }
31
- let rawMode = false;
32
- try {
33
- rawMode = stdin.isRaw || false;
34
- stdin.setRawMode(true);
35
- stdin.resume();
36
- stdin.setEncoding('utf8');
17
+ else if (Array.isArray(styledResult)) {
18
+ return ['', ...styledResult];
37
19
  }
38
- catch (err) {
39
- // EIO error - stdin not available
40
- resolve();
41
- return;
20
+ else {
21
+ return ['', promptText];
42
22
  }
43
- const onData = (key) => {
44
- // Handle spacebar, enter, or 'q'
45
- if (key === ' ' || key === '\r' || key === '\n' || key === 'q' || key === 'Q') {
46
- cleanup();
47
- resolve();
48
- }
49
- // Handle Ctrl+C
50
- if (key === '\u0003') {
51
- cleanup();
52
- process.exit(0);
53
- }
54
- };
55
- const onSIGINT = () => {
56
- cleanup();
57
- process.exit(0);
58
- };
59
- const cleanup = () => {
60
- stdin.removeListener('data', onData);
61
- // CRITICAL: Remove SIGINT listener to prevent memory leak
62
- // Use removeListener instead of once to ensure we can clean it up
63
- process.removeListener('SIGINT', onSIGINT);
64
- try {
65
- stdin.setRawMode(rawMode);
66
- stdin.pause();
67
- }
68
- catch (err) {
69
- // Ignore errors
70
- }
71
- };
72
- stdin.on('data', onData);
73
- // Use on() instead of once() so we can remove it in cleanup
74
- process.on('SIGINT', onSIGINT);
75
- });
23
+ };
76
24
  }
77
25
  //# sourceMappingURL=prompt.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/components/prompt.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/components/prompt.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAKpE,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAQlC;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,OAAsB;IAC3C,MAAM,EACJ,OAAO,EACP,GAAG,GAAG,UAAU,EAChB,KAAK,EAAE,WAAW,GAAG,aAAa,GACnC,GAAG,OAAO,CAAC;IAEZ,OAAO,CAAC,GAAG,EAAE,EAAE;QACb,MAAM,UAAU,GAAG,SAAS,GAAG,OAAO,OAAO,KAAK,CAAC;QACnD,MAAM,eAAe,GAAG,MAAM,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,UAAU,CAAC,CAAC;QACnE,MAAM,YAAY,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;QAE1C,6BAA6B;QAC7B,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;YACrC,OAAO,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;QAC5B,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;YACvC,OAAO,CAAC,EAAE,EAAE,GAAG,YAAY,CAAC,CAAC;QACjC,CAAC;aAAM,CAAC;YACJ,OAAO,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;QAC5B,CAAC;IACD,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { Component } from '../component';
2
+ import type { Color } from '../types';
3
+ export interface SectionOptions {
4
+ title: string;
5
+ titleColor?: Color;
6
+ borderColor?: Color;
7
+ padding?: number;
8
+ left?: boolean;
9
+ right?: boolean;
10
+ top?: boolean;
11
+ bottom?: boolean;
12
+ }
13
+ /**
14
+ * Section component - wraps content in a box with a tabbed title
15
+ *
16
+ * The title appears in a "tab" at the top, and the content is wrapped in a box.
17
+ * Uses rounded corners (╭ ╮ ╰ ╯) for a clean look. You can control which borders
18
+ * are shown using `left`, `right`, `top`, and `bottom` options.
19
+ *
20
+ * @example
21
+ * // Default: all borders shown
22
+ * Section({ title: 'My Section' }, ...)
23
+ *
24
+ * @example
25
+ * // Only left and right borders (no top/bottom)
26
+ * Section({ title: 'My Section', top: false, bottom: false }, ...)
27
+ *
28
+ * @example
29
+ * // Only left border
30
+ * Section({ title: 'My Section', right: false, top: false, bottom: false }, ...)
31
+ */
32
+ export declare function Section(options: SectionOptions, ...children: Component[]): Component;
33
+ //# sourceMappingURL=section.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section.d.ts","sourceRoot":"","sources":["../../src/components/section.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAGtC,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,KAAK,CAAC;IACnB,WAAW,CAAC,EAAE,KAAK,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,OAAO,CACrB,OAAO,EAAE,cAAc,EACvB,GAAG,QAAQ,EAAE,SAAS,EAAE,GACvB,SAAS,CAyLX"}
@@ -0,0 +1,178 @@
1
+ // Section component - wraps content in a box with a tabbed title
2
+ import { renderChildren } from '../component';
3
+ import { applyStyle } from '../utils/colors';
4
+ /**
5
+ * Section component - wraps content in a box with a tabbed title
6
+ *
7
+ * The title appears in a "tab" at the top, and the content is wrapped in a box.
8
+ * Uses rounded corners (╭ ╮ ╰ ╯) for a clean look. You can control which borders
9
+ * are shown using `left`, `right`, `top`, and `bottom` options.
10
+ *
11
+ * @example
12
+ * // Default: all borders shown
13
+ * Section({ title: 'My Section' }, ...)
14
+ *
15
+ * @example
16
+ * // Only left and right borders (no top/bottom)
17
+ * Section({ title: 'My Section', top: false, bottom: false }, ...)
18
+ *
19
+ * @example
20
+ * // Only left border
21
+ * Section({ title: 'My Section', right: false, top: false, bottom: false }, ...)
22
+ */
23
+ export function Section(options, ...children) {
24
+ return (ctx) => {
25
+ const { title, titleColor = 'brightCyan', borderColor = 'brightBlack', padding = 1, left = true, right = true, top = true, bottom = true, } = options;
26
+ const availableWidth = ctx.availableWidth;
27
+ const titleText = ` ${title} `;
28
+ const titleWidth = titleText.length;
29
+ // Calculate box width (full available width)
30
+ const boxWidth = availableWidth;
31
+ // Calculate content width: subtract borders and padding
32
+ const borderWidth = (left ? 1 : 0) + (right ? 1 : 0);
33
+ const contentWidth = Math.max(0, boxWidth - borderWidth - (padding * 2));
34
+ // Render children - simple and clean!
35
+ const contentLines = renderChildren(children, {
36
+ ...ctx,
37
+ availableWidth: contentWidth,
38
+ });
39
+ // Build the section with tab and box (even if empty)
40
+ const lines = [];
41
+ // Always use rounded corners when showing them
42
+ const topLeftChar = '╭';
43
+ const topRightChar = '╮';
44
+ const bottomLeftChar = '╰';
45
+ const bottomRightChar = '╯';
46
+ const tabHorizontal = applyStyle('─', { color: borderColor });
47
+ const vertical = applyStyle('│', { color: borderColor });
48
+ const paddingSpaces = ' '.repeat(padding);
49
+ // Top border/tab line: ╭─ Title ────────────────╮
50
+ if (top) {
51
+ const tabTitle = applyStyle(titleText, { color: titleColor });
52
+ let tabLine = '';
53
+ // Top-left corner: always shown when top line is visible (top is true here)
54
+ // Also shown if left border is visible
55
+ tabLine += applyStyle(topLeftChar, { color: borderColor });
56
+ // Title
57
+ tabLine += tabTitle;
58
+ // Horizontal line after title
59
+ const spaceAfterTitle = Math.max(0, boxWidth - titleWidth - 1 - 1); // minus both corners
60
+ tabLine += tabHorizontal.repeat(spaceAfterTitle);
61
+ // Top-right corner: always shown when top line is visible (top is true here)
62
+ // Also shown if right border is visible
63
+ tabLine += applyStyle(topRightChar, { color: borderColor });
64
+ lines.push(tabLine);
65
+ }
66
+ // Calculate which line is first and last (excluding top/bottom border lines)
67
+ const totalContentLines = padding + contentLines.length + padding;
68
+ let lineIndex = 0;
69
+ // Top padding: empty lines with borders
70
+ for (let i = 0; i < padding; i++) {
71
+ const isFirstLine = lineIndex === 0;
72
+ const isLastLine = lineIndex === totalContentLines - 1;
73
+ let line = '';
74
+ // Left border: show top-left corner on first line if left is visible (unless top already added it)
75
+ if (left && isFirstLine && !top) {
76
+ line += applyStyle(topLeftChar, { color: borderColor });
77
+ }
78
+ else if (left && isLastLine && !bottom) {
79
+ // Left border: show bottom-left corner on last line if left is visible (unless bottom already added it)
80
+ line += applyStyle(bottomLeftChar, { color: borderColor });
81
+ }
82
+ else if (left) {
83
+ line += vertical;
84
+ }
85
+ line += paddingSpaces + ' '.repeat(contentWidth) + paddingSpaces;
86
+ // Right border: show top-right corner on first line if right is visible (unless top already added it)
87
+ if (right && isFirstLine && !top) {
88
+ line += applyStyle(topRightChar, { color: borderColor });
89
+ }
90
+ else if (right && isLastLine && !bottom) {
91
+ // Right border: show bottom-right corner on last line if right is visible (unless bottom already added it)
92
+ line += applyStyle(bottomRightChar, { color: borderColor });
93
+ }
94
+ else if (right) {
95
+ line += vertical;
96
+ }
97
+ lines.push(line);
98
+ lineIndex++;
99
+ }
100
+ // Content lines - children already know their available width
101
+ for (let i = 0; i < contentLines.length; i++) {
102
+ const isFirstLine = lineIndex === 0;
103
+ const isLastLine = lineIndex === totalContentLines - 1;
104
+ const contentLine = contentLines[i];
105
+ let line = '';
106
+ // Left border: show top-left corner on first line if left is visible (unless top already added it)
107
+ if (left && isFirstLine && !top) {
108
+ line += applyStyle(topLeftChar, { color: borderColor });
109
+ }
110
+ else if (left && isLastLine && !bottom) {
111
+ // Left border: show bottom-left corner on last line if left is visible (unless bottom already added it)
112
+ line += applyStyle(bottomLeftChar, { color: borderColor });
113
+ }
114
+ else if (left) {
115
+ line += vertical;
116
+ }
117
+ line += paddingSpaces + contentLine + paddingSpaces;
118
+ // Right border: show top-right corner on first line if right is visible (unless top already added it)
119
+ if (right && isFirstLine && !top) {
120
+ line += applyStyle(topRightChar, { color: borderColor });
121
+ }
122
+ else if (right && isLastLine && !bottom) {
123
+ // Right border: show bottom-right corner on last line if right is visible (unless bottom already added it)
124
+ line += applyStyle(bottomRightChar, { color: borderColor });
125
+ }
126
+ else if (right) {
127
+ line += vertical;
128
+ }
129
+ lines.push(line);
130
+ lineIndex++;
131
+ }
132
+ // Bottom padding: empty lines with borders
133
+ for (let i = 0; i < padding; i++) {
134
+ const isFirstLine = lineIndex === 0;
135
+ const isLastLine = lineIndex === totalContentLines - 1;
136
+ let line = '';
137
+ // Left border: show top-left corner on first line if left is visible (unless top already added it)
138
+ if (left && isFirstLine && !top) {
139
+ line += applyStyle(topLeftChar, { color: borderColor });
140
+ }
141
+ else if (left && isLastLine && !bottom) {
142
+ // Left border: show bottom-left corner on last line if left is visible (unless bottom already added it)
143
+ line += applyStyle(bottomLeftChar, { color: borderColor });
144
+ }
145
+ else if (left) {
146
+ line += vertical;
147
+ }
148
+ line += paddingSpaces + ' '.repeat(contentWidth) + paddingSpaces;
149
+ // Right border: show top-right corner on first line if right is visible (unless top already added it)
150
+ if (right && isFirstLine && !top) {
151
+ line += applyStyle(topRightChar, { color: borderColor });
152
+ }
153
+ else if (right && isLastLine && !bottom) {
154
+ // Right border: show bottom-right corner on last line if right is visible (unless bottom already added it)
155
+ line += applyStyle(bottomRightChar, { color: borderColor });
156
+ }
157
+ else if (right) {
158
+ line += vertical;
159
+ }
160
+ lines.push(line);
161
+ lineIndex++;
162
+ }
163
+ // Bottom border: ╰───────────────────────╯
164
+ if (bottom) {
165
+ let bottomLine = '';
166
+ // Bottom-left corner: always shown when bottom line is visible
167
+ bottomLine += applyStyle(bottomLeftChar, { color: borderColor });
168
+ // Horizontal line
169
+ const horizontalWidth = Math.max(0, boxWidth - 1 - 1); // minus both corners
170
+ bottomLine += tabHorizontal.repeat(horizontalWidth);
171
+ // Bottom-right corner: always shown when bottom line is visible
172
+ bottomLine += applyStyle(bottomRightChar, { color: borderColor });
173
+ lines.push(bottomLine);
174
+ }
175
+ return lines;
176
+ };
177
+ }
178
+ //# sourceMappingURL=section.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section.js","sourceRoot":"","sources":["../../src/components/section.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { Component } from '../component';
2
+ import type { Color } from '../types';
3
+ export type BorderStyle = 'cap' | 'capHalf' | 'brace' | 'dot' | 'asterisk';
4
+ export interface Segment {
5
+ content: string;
6
+ color?: Color;
7
+ borderStyle?: BorderStyle;
8
+ }
9
+ export interface SegmentsOptions {
10
+ segments: Segment[];
11
+ }
12
+ /**
13
+ * Segments component - renders decorated segments with automatic dividers
14
+ *
15
+ * Segments are decorated with borders/symbols on the default terminal background,
16
+ * and automatically connected with dash dividers (─). Content is automatically padded.
17
+ *
18
+ * Simple API:
19
+ * - content: Text content (padding added automatically)
20
+ * - color: Text color (borders use same color)
21
+ * - borderStyle: Border decoration style (default: 'cap')
22
+ *
23
+ * Border styles: 'cap' (default), 'capHalf', 'brace', 'dot', 'asterisk'
24
+ */
25
+ export declare function Segments(options: SegmentsOptions): Component;
26
+ //# sourceMappingURL=segments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segments.d.ts","sourceRoot":"","sources":["../../src/components/segments.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAKtC,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,SAAS,GAAG,OAAO,GAAG,KAAK,GAAG,UAAU,CAAC;AAE3E,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAiCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,CAkE5D"}
@@ -0,0 +1,105 @@
1
+ // Segments component - OhMyZsh/Powerline-style colored segments with angled dividers
2
+ import { applyStyle } from '../utils/colors';
3
+ import { truncateToWidth } from '../utils/text';
4
+ /**
5
+ * Get border characters for segment decoration
6
+ * Creates borders around segments on default terminal background
7
+ */
8
+ function getBorderChars(style) {
9
+ switch (style) {
10
+ case 'cap':
11
+ // Half-filled circles - the favorite!
12
+ // ◐ (U+25D0) = Circle with left half black
13
+ // ◑ (U+25D1) = Circle with right half black
14
+ return { left: '◐', right: '◑' };
15
+ case 'capHalf':
16
+ // Upper half circles - mirrored to face the text
17
+ // ◖ (U+25D6) = Upper half circle (left)
18
+ // ◗ (U+25D7) = Lower half circle (right, creates mirror effect)
19
+ return { left: '◖', right: '◗' };
20
+ case 'brace':
21
+ // Curly braces - classic and clean
22
+ return { left: '{', right: '}' };
23
+ case 'dot':
24
+ // Small dot/point
25
+ // · (U+00B7) = Middle dot (smaller, lighter)
26
+ return { left: '·', right: '·' };
27
+ case 'asterisk':
28
+ // Asterisk/star points
29
+ // * (U+002A) = Asterisk
30
+ return { left: '*', right: '*' };
31
+ }
32
+ }
33
+ /**
34
+ * Segments component - renders decorated segments with automatic dividers
35
+ *
36
+ * Segments are decorated with borders/symbols on the default terminal background,
37
+ * and automatically connected with dash dividers (─). Content is automatically padded.
38
+ *
39
+ * Simple API:
40
+ * - content: Text content (padding added automatically)
41
+ * - color: Text color (borders use same color)
42
+ * - borderStyle: Border decoration style (default: 'cap')
43
+ *
44
+ * Border styles: 'cap' (default), 'capHalf', 'brace', 'dot', 'asterisk'
45
+ */
46
+ export function Segments(options) {
47
+ return (ctx) => {
48
+ const { segments } = options;
49
+ if (segments.length === 0) {
50
+ return '';
51
+ }
52
+ const availableWidth = ctx.availableWidth;
53
+ let result = '';
54
+ let usedWidth = 0;
55
+ for (let i = 0; i < segments.length; i++) {
56
+ const segment = segments[i];
57
+ const nextSegment = i < segments.length - 1 ? segments[i + 1] : null;
58
+ // Left divider (automatic - dash for middle segments)
59
+ if (i > 0) {
60
+ // Always use dash (─) for dividers - it works well with all border styles
61
+ const dividerColor = 'brightBlack';
62
+ const styledDivider = applyStyle('─', { color: dividerColor });
63
+ result += styledDivider;
64
+ usedWidth += 1;
65
+ }
66
+ // Left border decoration
67
+ const borderStyle = segment.borderStyle ?? 'cap';
68
+ const borderChars = getBorderChars(borderStyle);
69
+ if (borderChars.left) {
70
+ // Auto-theme: borders use text color
71
+ const borderColor = segment.color ?? 'brightCyan';
72
+ result += applyStyle(borderChars.left, { color: borderColor });
73
+ usedWidth += 1;
74
+ }
75
+ // Segment content with automatic padding (no background, just text color)
76
+ // Trim content and add padding automatically
77
+ const trimmedContent = segment.content.trim();
78
+ const paddedContent = ` ${trimmedContent} `;
79
+ const styledContent = applyStyle(paddedContent, {
80
+ color: segment.color,
81
+ // No backgroundColor - uses default terminal background
82
+ });
83
+ result += styledContent;
84
+ usedWidth += paddedContent.length;
85
+ // Right border decoration
86
+ if (borderChars.right) {
87
+ // Auto-theme: borders use text color
88
+ const borderColor = segment.color ?? 'brightCyan';
89
+ result += applyStyle(borderChars.right, { color: borderColor });
90
+ usedWidth += 1;
91
+ }
92
+ // Right divider (automatic - dash for middle segments)
93
+ if (i < segments.length - 1) {
94
+ // Always use dash (─) for dividers - it works well with all border styles
95
+ const dividerColor = 'brightBlack';
96
+ const styledDivider = applyStyle('─', { color: dividerColor });
97
+ result += styledDivider;
98
+ usedWidth += 1;
99
+ }
100
+ }
101
+ // Truncate if needed (preserving ANSI codes)
102
+ return truncateToWidth(result, availableWidth);
103
+ };
104
+ }
105
+ //# sourceMappingURL=segments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segments.js","sourceRoot":"","sources":["../../src/components/segments.ts"],"names":[],"mappings":"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"}
@@ -1,18 +1,20 @@
1
- import { TerminalRegion } from '../region';
2
- import type { SpinnerOptions } from '../types';
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;
1
+ import type { Component } from '../component';
2
+ import type { Color } from '../types';
3
+ export type SpinnerStyle = 'classic-dots' | 'bouncing-bar';
4
+ export interface SpinnerOptions {
5
+ style?: SpinnerStyle;
6
+ frames?: string[];
7
+ interval?: number;
8
+ color?: Color;
9
+ autoStart?: boolean;
17
10
  }
11
+ /**
12
+ * Create a spinner component that manages its own animation state
13
+ * Returns an object with render(), start(), and stop() methods
14
+ */
15
+ export declare function Spinner(options?: SpinnerOptions): {
16
+ render: Component;
17
+ start: () => void;
18
+ stop: () => void;
19
+ };
18
20
  //# sourceMappingURL=spinner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../../src/components/spinner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAI/C,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;IAWb,IAAI,IAAI,IAAI;IAUZ,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAK3B,OAAO,CAAC,MAAM;CAQf"}
1
+ {"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../../src/components/spinner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,cAAc,CAAC;AAC7D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAGtC,MAAM,MAAM,YAAY,GAAG,cAAc,GAAG,cAAc,CAAC;AAE3D,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAQD;;;GAGG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG;IACrD,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAoEA"}
@@ -1,50 +1,66 @@
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
- // Render immediately when starting
22
- this.render();
23
- this.interval = setInterval(() => {
24
- this.frameIndex = (this.frameIndex + 1) % this.frames.length;
25
- this.render();
26
- }, this.intervalMs);
27
- }
28
- stop() {
29
- if (!this.isRunning)
30
- return;
31
- this.isRunning = false;
32
- if (this.interval) {
33
- clearInterval(this.interval);
1
+ // Spinner component - manages its own animation state
2
+ import { applyStyle } from '../utils/colors';
3
+ // Built-in spinner frame definitions
4
+ const SPINNER_FRAMES = {
5
+ 'classic-dots': ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'], // Clockwise rotation with 3 connected dots
6
+ 'bouncing-bar': ['▁', '', '▃', '▄', '▅', '▆', '▇', '█', '▇', '▆', '▅', '▄', '▃', '▂'],
7
+ };
8
+ /**
9
+ * Create a spinner component that manages its own animation state
10
+ * Returns an object with render(), start(), and stop() methods
11
+ */
12
+ export function Spinner(options = {}) {
13
+ const { style, frames, interval = 80, color = 'yellow', autoStart = true, } = options;
14
+ // Determine which frames to use: custom frames override style
15
+ const finalFrames = frames ?? (style ? SPINNER_FRAMES[style] : ['⠇', '⠋', '⠙', '⠴', '⠋', '⠙', '⠸', '⠴', '⠦']);
16
+ let currentFrameIndex = 0;
17
+ let intervalId = null;
18
+ let onUpdateCallback = null;
19
+ let cleanupRegistered = false;
20
+ const render = (ctx) => {
21
+ // Store the onUpdate callback from context (set by region)
22
+ if (ctx.onUpdate && !onUpdateCallback) {
23
+ onUpdateCallback = ctx.onUpdate;
24
+ // Start automatically if enabled and callback is available
25
+ if (autoStart) {
26
+ start();
27
+ }
34
28
  }
35
- // Clear the spinner line
36
- this.region.setLine(this.lineNumber, '');
37
- }
38
- setText(text) {
39
- this.text = text;
40
- this.render();
41
- }
42
- render() {
43
- const frame = this.frames[this.frameIndex];
44
- const line = `${frame} ${this.text}`;
45
- // Just update the line - Zig handles batching and rendering
46
- this.region.setLine(this.lineNumber, line);
47
- // Spinner updates frequently, so Zig's throttling will handle smooth animation
48
- }
29
+ // Register cleanup callback to stop the interval when component is removed (only once)
30
+ if (ctx.onCleanup && !cleanupRegistered) {
31
+ cleanupRegistered = true;
32
+ ctx.onCleanup(() => {
33
+ stop();
34
+ });
35
+ }
36
+ const frame = finalFrames[currentFrameIndex];
37
+ if (color) {
38
+ return applyStyle(frame, { color });
39
+ }
40
+ return frame;
41
+ };
42
+ const start = () => {
43
+ if (intervalId !== null) {
44
+ return; // Already running
45
+ }
46
+ intervalId = setInterval(() => {
47
+ currentFrameIndex = (currentFrameIndex + 1) % finalFrames.length;
48
+ // Trigger re-render using the callback from RenderContext
49
+ if (onUpdateCallback) {
50
+ onUpdateCallback();
51
+ }
52
+ }, interval);
53
+ };
54
+ const stop = () => {
55
+ if (intervalId !== null) {
56
+ clearInterval(intervalId);
57
+ intervalId = null;
58
+ }
59
+ };
60
+ return {
61
+ render,
62
+ start,
63
+ stop,
64
+ };
49
65
  }
50
66
  //# sourceMappingURL=spinner.js.map