ink-prompt 0.1.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 (31) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/dist/components/MultilineInput/KeyHandler.d.ts +16 -0
  4. package/dist/components/MultilineInput/KeyHandler.js +124 -0
  5. package/dist/components/MultilineInput/TextBuffer.d.ts +52 -0
  6. package/dist/components/MultilineInput/TextBuffer.js +312 -0
  7. package/dist/components/MultilineInput/TextRenderer.d.ts +24 -0
  8. package/dist/components/MultilineInput/TextRenderer.js +80 -0
  9. package/dist/components/MultilineInput/__tests__/KeyHandler.test.d.ts +1 -0
  10. package/dist/components/MultilineInput/__tests__/KeyHandler.test.js +124 -0
  11. package/dist/components/MultilineInput/__tests__/TextBuffer.test.d.ts +1 -0
  12. package/dist/components/MultilineInput/__tests__/TextBuffer.test.js +450 -0
  13. package/dist/components/MultilineInput/__tests__/TextRenderer.test.d.ts +1 -0
  14. package/dist/components/MultilineInput/__tests__/TextRenderer.test.js +185 -0
  15. package/dist/components/MultilineInput/__tests__/integration.test.d.ts +1 -0
  16. package/dist/components/MultilineInput/__tests__/integration.test.js +88 -0
  17. package/dist/components/MultilineInput/__tests__/useTextInput.test.d.ts +1 -0
  18. package/dist/components/MultilineInput/__tests__/useTextInput.test.js +172 -0
  19. package/dist/components/MultilineInput/index.d.ts +45 -0
  20. package/dist/components/MultilineInput/index.js +132 -0
  21. package/dist/components/MultilineInput/types.d.ts +55 -0
  22. package/dist/components/MultilineInput/types.js +1 -0
  23. package/dist/components/MultilineInput/useTextInput.d.ts +22 -0
  24. package/dist/components/MultilineInput/useTextInput.js +108 -0
  25. package/dist/hello.test.d.ts +1 -0
  26. package/dist/hello.test.js +13 -0
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.js +2 -0
  29. package/dist/utils/logger.d.ts +15 -0
  30. package/dist/utils/logger.js +42 -0
  31. package/package.json +57 -0
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Cursor position in the text buffer
3
+ */
4
+ export interface Cursor {
5
+ /** 0-indexed line number */
6
+ line: number;
7
+ /** 0-indexed column position */
8
+ column: number;
9
+ }
10
+ /**
11
+ * Text buffer containing multiple lines
12
+ */
13
+ export interface Buffer {
14
+ /** Array of text lines (without newline characters) */
15
+ lines: string[];
16
+ }
17
+ /**
18
+ * Cursor movement directions
19
+ */
20
+ export type Direction = 'up' | 'down' | 'left' | 'right' | 'lineStart' | 'lineEnd';
21
+ /**
22
+ * Result of wrapping buffer lines for visual display
23
+ */
24
+ export interface WrapResult {
25
+ /** Visual lines after wrapping */
26
+ visualLines: string[];
27
+ /** Row in visual lines where cursor appears */
28
+ cursorVisualRow: number;
29
+ /** Column in that visual row where cursor appears */
30
+ cursorVisualCol: number;
31
+ }
32
+ /**
33
+ * Keyboard key state (mirrors Ink's Key interface)
34
+ * Defined locally to avoid ESM/CJS import issues with Ink
35
+ */
36
+ export interface Key {
37
+ upArrow?: boolean;
38
+ downArrow?: boolean;
39
+ leftArrow?: boolean;
40
+ rightArrow?: boolean;
41
+ pageDown?: boolean;
42
+ pageUp?: boolean;
43
+ return?: boolean;
44
+ escape?: boolean;
45
+ ctrl?: boolean;
46
+ shift?: boolean;
47
+ tab?: boolean;
48
+ backspace?: boolean;
49
+ delete?: boolean;
50
+ meta?: boolean;
51
+ /** Home key (may not be available in all terminals) */
52
+ home?: boolean;
53
+ /** End key (may not be available in all terminals) */
54
+ end?: boolean;
55
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import type { Cursor, Direction } from './types.js';
2
+ export interface UseTextInputProps {
3
+ initialValue?: string;
4
+ /** Terminal width for visual-aware cursor navigation (up/down arrows respect line wrapping) */
5
+ width?: number;
6
+ }
7
+ export interface UseTextInputResult {
8
+ value: string;
9
+ cursor: Cursor;
10
+ insert: (char: string) => void;
11
+ delete: () => void;
12
+ deleteForward: () => void;
13
+ newLine: () => void;
14
+ deleteAndNewLine: () => void;
15
+ moveCursor: (direction: Direction) => void;
16
+ undo: () => void;
17
+ redo: () => void;
18
+ setText: (text: string) => void;
19
+ cursorOffset: number;
20
+ setCursorOffset: (offset: number) => void;
21
+ }
22
+ export declare function useTextInput({ initialValue, width }?: UseTextInputProps): UseTextInputResult;
@@ -0,0 +1,108 @@
1
+ import { useState, useCallback } from 'react';
2
+ import { createBuffer, insertText as bufferInsertText, deleteChar as bufferDeleteChar, deleteCharForward as bufferDeleteCharForward, insertNewLine as bufferInsertNewLine, moveCursor as bufferMoveCursor, getTextContent, getOffset, getCursor, } from './TextBuffer.js';
3
+ import { log } from '../../utils/logger.js';
4
+ export function useTextInput({ initialValue = '', width } = {}) {
5
+ const [buffer, setBuffer] = useState(() => createBuffer(initialValue));
6
+ const [cursor, setCursor] = useState(() => {
7
+ const lines = initialValue.split('\n');
8
+ return {
9
+ line: lines.length - 1,
10
+ column: lines[lines.length - 1].length,
11
+ };
12
+ });
13
+ const [undoStack, setUndoStack] = useState([]);
14
+ const [redoStack, setRedoStack] = useState([]);
15
+ const pushToHistory = useCallback((currentBuffer, currentCursor) => {
16
+ setUndoStack((prev) => [...prev, { buffer: currentBuffer, cursor: currentCursor }]);
17
+ setRedoStack([]);
18
+ }, []);
19
+ const insert = useCallback((char) => {
20
+ log(`[INSERT] char="${char.replace(/[\x00-\x1F\x7F-\uFFFF]/g, c => `\\x${c.charCodeAt(0).toString(16)}`)}" len=${char.length} cursor={line:${cursor.line},col:${cursor.column}} linesBefore=${buffer.lines.length}`);
21
+ // Normalize line endings: \r\n → \n, \r → \n (handles Windows, Unix, and old Mac)
22
+ const normalized = char.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
23
+ pushToHistory(buffer, cursor);
24
+ // TextBuffer now handles multi-line insertion internally
25
+ const result = bufferInsertText(buffer, cursor, normalized);
26
+ setBuffer(result.buffer);
27
+ setCursor(result.cursor);
28
+ }, [buffer, cursor, pushToHistory]);
29
+ const deleteChar = useCallback(() => {
30
+ pushToHistory(buffer, cursor);
31
+ const result = bufferDeleteChar(buffer, cursor);
32
+ setBuffer(result.buffer);
33
+ setCursor(result.cursor);
34
+ }, [buffer, cursor, pushToHistory]);
35
+ const deleteCharForward = useCallback(() => {
36
+ pushToHistory(buffer, cursor);
37
+ const result = bufferDeleteCharForward(buffer, cursor);
38
+ setBuffer(result.buffer);
39
+ setCursor(result.cursor);
40
+ }, [buffer, cursor, pushToHistory]);
41
+ const newLine = useCallback(() => {
42
+ pushToHistory(buffer, cursor);
43
+ const result = bufferInsertNewLine(buffer, cursor);
44
+ setBuffer(result.buffer);
45
+ setCursor(result.cursor);
46
+ }, [buffer, cursor, pushToHistory]);
47
+ const deleteAndNewLine = useCallback(() => {
48
+ pushToHistory(buffer, cursor);
49
+ // First delete the character before cursor (the backslash)
50
+ const afterDelete = bufferDeleteChar(buffer, cursor);
51
+ // Then insert newline using the updated buffer and cursor
52
+ const afterNewLine = bufferInsertNewLine(afterDelete.buffer, afterDelete.cursor);
53
+ setBuffer(afterNewLine.buffer);
54
+ setCursor(afterNewLine.cursor);
55
+ }, [buffer, cursor, pushToHistory]);
56
+ const moveCursor = useCallback((direction) => {
57
+ const newCursor = bufferMoveCursor(buffer, cursor, direction, width);
58
+ setCursor(newCursor);
59
+ }, [buffer, cursor, width]);
60
+ const undo = useCallback(() => {
61
+ if (undoStack.length === 0)
62
+ return;
63
+ const previousState = undoStack[undoStack.length - 1];
64
+ const newUndoStack = undoStack.slice(0, -1);
65
+ setRedoStack((prev) => [...prev, { buffer, cursor }]);
66
+ setBuffer(previousState.buffer);
67
+ setCursor(previousState.cursor);
68
+ setUndoStack(newUndoStack);
69
+ }, [buffer, cursor, undoStack]);
70
+ const redo = useCallback(() => {
71
+ if (redoStack.length === 0)
72
+ return;
73
+ const nextState = redoStack[redoStack.length - 1];
74
+ const newRedoStack = redoStack.slice(0, -1);
75
+ setUndoStack((prev) => [...prev, { buffer, cursor }]);
76
+ setBuffer(nextState.buffer);
77
+ setCursor(nextState.cursor);
78
+ setRedoStack(newRedoStack);
79
+ }, [buffer, cursor, redoStack]);
80
+ const setText = useCallback((text) => {
81
+ pushToHistory(buffer, cursor);
82
+ const newBuffer = createBuffer(text);
83
+ setBuffer(newBuffer);
84
+ // Move cursor to end of new text
85
+ const lines = text.split('\n');
86
+ setCursor({
87
+ line: lines.length - 1,
88
+ column: lines[lines.length - 1].length,
89
+ });
90
+ }, [buffer, cursor, pushToHistory]);
91
+ return {
92
+ value: getTextContent(buffer),
93
+ cursor,
94
+ insert,
95
+ delete: deleteChar,
96
+ deleteForward: deleteCharForward,
97
+ newLine,
98
+ deleteAndNewLine,
99
+ moveCursor,
100
+ undo,
101
+ redo,
102
+ setText,
103
+ cursorOffset: getOffset(buffer, cursor),
104
+ setCursorOffset: useCallback((offset) => {
105
+ setCursor(getCursor(buffer, offset));
106
+ }, [buffer]),
107
+ };
108
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ describe('Hello World Test', () => {
3
+ it('should pass a simple test', () => {
4
+ expect(true).toBe(true);
5
+ });
6
+ it('should perform basic arithmetic', () => {
7
+ expect(2 + 2).toBe(4);
8
+ });
9
+ it('should handle string concatenation', () => {
10
+ const greeting = 'Hello' + ' ' + 'World';
11
+ expect(greeting).toBe('Hello World');
12
+ });
13
+ });
@@ -0,0 +1,2 @@
1
+ export { MultilineInput } from './components/MultilineInput/index.js';
2
+ export type { MultilineInputProps } from './components/MultilineInput/index.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ // Re-export all components and utilities
2
+ export { MultilineInput } from './components/MultilineInput/index.js';
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Initialize the logger by clearing any existing log file.
3
+ */
4
+ export declare function initLogger(): void;
5
+ /**
6
+ * Log a message to the log file if it meets the minimum log level.
7
+ * @param message - The message to log
8
+ * @param level - The severity level of the message (default: INFO)
9
+ */
10
+ export declare function log(message: string, level?: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR'): void;
11
+ declare const _default: {
12
+ log: typeof log;
13
+ initLogger: typeof initLogger;
14
+ };
15
+ export default _default;
@@ -0,0 +1,42 @@
1
+ import { appendFileSync, existsSync, unlinkSync } from 'fs';
2
+ import { join } from 'path';
3
+ const LOG_FILE = process.env.INK_PROMPT_LOG_FILE || join(process.cwd(), 'ink-prompt.debug.log');
4
+ // Define log levels with numeric priority
5
+ const LOG_LEVELS = {
6
+ DEBUG: 0,
7
+ INFO: 1,
8
+ WARN: 2,
9
+ ERROR: 3,
10
+ };
11
+ // Get current log level from env, default to ERROR (or DEBUG if you prefer)
12
+ const CURRENT_LOG_LEVEL = process.env.LOG_LEVEL?.toUpperCase() || 'ERROR';
13
+ const CURRENT_LOG_LEVEL_PRIORITY = LOG_LEVELS[CURRENT_LOG_LEVEL] ?? LOG_LEVELS.INFO;
14
+ /**
15
+ * Initialize the logger by clearing any existing log file.
16
+ */
17
+ export function initLogger() {
18
+ if (existsSync(LOG_FILE)) {
19
+ unlinkSync(LOG_FILE);
20
+ }
21
+ }
22
+ /**
23
+ * Log a message to the log file if it meets the minimum log level.
24
+ * @param message - The message to log
25
+ * @param level - The severity level of the message (default: INFO)
26
+ */
27
+ export function log(message, level = 'INFO') {
28
+ // Check if we should log based on level priority
29
+ if ((LOG_LEVELS[level] ?? 1) < CURRENT_LOG_LEVEL_PRIORITY) {
30
+ return;
31
+ }
32
+ try {
33
+ appendFileSync(LOG_FILE, `${new Date().toISOString()} [${level}] ${message}\n`);
34
+ }
35
+ catch (error) {
36
+ // Silently fail to avoid crashing the application
37
+ if (process.env.DEBUG) {
38
+ console.error('Failed to write to log file:', error);
39
+ }
40
+ }
41
+ }
42
+ export default { log, initLogger };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "ink-prompt",
3
+ "version": "0.1.2",
4
+ "description": "A React Ink component for prompts",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "dev": "tsc --watch",
22
+ "type-check": "tsc --noEmit",
23
+ "clean": "rm -rf dist",
24
+ "test": "vitest run",
25
+ "test:ui": "vitest --ui",
26
+ "test:watch": "vitest",
27
+ "release": "bash scripts/release.sh"
28
+ },
29
+ "keywords": [
30
+ "ink",
31
+ "react",
32
+ "prompt",
33
+ "cli"
34
+ ],
35
+ "author": "Duc Nguyen",
36
+ "license": "MIT",
37
+ "repository": "github:qduc/ink-prompt",
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "peerDependencies": {
42
+ "ink": "^4.0.0 || ^5.0.0 || ^6.0.0",
43
+ "react": "^18.0.0"
44
+ },
45
+ "devDependencies": {
46
+ "@testing-library/react": "^16.3.0",
47
+ "@types/node": "^20.0.0",
48
+ "@types/react": "^19.0.0",
49
+ "@vitest/ui": "^4.0.15",
50
+ "happy-dom": "^20.0.11",
51
+ "ink": "^6.5.1",
52
+ "react": "^19.2.1",
53
+ "react-dom": "^19.2.1",
54
+ "typescript": "^5.0.0",
55
+ "vitest": "^4.0.15"
56
+ }
57
+ }