@contentful/experience-design-system-cli 2.7.0 → 2.7.1-dev-build-02830eb.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/package.json +1 -1
- package/dist/src/analyze/select/tui/App.js +54 -475
- package/dist/src/analyze/select/tui/components/ComponentDetail.d.ts +3 -5
- package/dist/src/analyze/select/tui/components/ComponentDetail.js +3 -3
- package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +1 -1
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +2 -10
- package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +1 -1
- package/dist/src/analyze/select/tui/components/HelpOverlay.js +2 -7
- package/dist/src/analyze/select/tui/components/JsonEditor.d.ts +7 -5
- package/dist/src/analyze/select/tui/components/JsonEditor.js +15 -146
- package/dist/src/analyze/select/tui/components/QuitDialog.d.ts +1 -1
- package/dist/src/analyze/select/tui/components/QuitDialog.js +2 -10
- package/dist/src/analyze/select/tui/components/Sidebar.d.ts +0 -2
- package/dist/src/analyze/select/tui/components/StatusBar.d.ts +0 -2
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +5 -0
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +7 -4
- package/dist/src/analyze/select/tui/hooks/useKeymap.d.ts +0 -23
- package/dist/src/analyze/select/tui/hooks/useKeymap.js +1 -67
- package/dist/src/analyze/select/tui/hooks/useRawMode.d.ts +6 -0
- package/dist/src/analyze/select/tui/hooks/useRawMode.js +16 -0
- package/dist/src/analyze/select/tui/hooks/useSideEffects.d.ts +17 -0
- package/dist/src/analyze/select/tui/hooks/useSideEffects.js +226 -0
- package/dist/src/analyze/select/tui/hooks/useUndo.js +15 -21
- package/dist/src/analyze/select/tui/inputToAction.d.ts +13 -0
- package/dist/src/analyze/select/tui/inputToAction.js +68 -0
- package/dist/src/analyze/select/tui/state.d.ts +136 -0
- package/dist/src/analyze/select/tui/state.js +385 -0
- package/dist/src/analyze/select/tui/utils.d.ts +5 -0
- package/dist/src/analyze/select/tui/utils.js +11 -0
- package/dist/src/analyze/tui/AnalyzeView.js +2 -0
- package/dist/src/import/tui/WizardApp.js +2 -0
- package/dist/src/import/tui/steps/GenerateReviewStep.js +4 -11
- package/dist/src/print/validate/tui/ValidateView.js +2 -0
- package/package.json +2 -2
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
|
|
4
|
-
export function FinalizeDialog({ accepted, rejected, needsReview, onConfirm, onCancel, }) {
|
|
5
|
-
useImmediateInput((input, key) => {
|
|
6
|
-
if (input === 'y' || key.return) {
|
|
7
|
-
onConfirm();
|
|
8
|
-
}
|
|
9
|
-
else if (input === 'n' || key.escape) {
|
|
10
|
-
onCancel();
|
|
11
|
-
}
|
|
12
|
-
});
|
|
3
|
+
// Pure render — input handled by inputToAction in App.
|
|
4
|
+
export function FinalizeDialog({ accepted, rejected, needsReview, onConfirm: _onConfirm, onCancel: _onCancel, }) {
|
|
13
5
|
const allResolved = needsReview === 0;
|
|
14
6
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", padding: 1, width: 58, children: [_jsx(Text, { bold: true, children: '─'.repeat(17) + ' Finalize ' + '─'.repeat(17) }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsxs(Text, { color: "green", children: [accepted, " accepted"] }), _jsx(Text, { children: " \u00B7 " }), _jsxs(Text, { color: "red", children: [rejected, " rejected"] }), _jsx(Text, { children: " \u00B7 " }), _jsxs(Text, { dimColor: true, children: [needsReview, " unresolved"] })] }), _jsx(Text, { children: " " }), !allResolved && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "yellow", children: '⚠ ' + needsReview + ' component' + (needsReview === 1 ? ' is' : 's are') + ' unresolved and will be' }), _jsx(Text, { color: "yellow", children: ' excluded from the output.' }), _jsx(Text, { children: " " })] })), _jsx(Text, { children: allResolved ? 'Save decisions and exit? All components resolved.' : 'Save decisions and exit?' }), _jsx(Text, { children: " " }), _jsx(Text, { children: ' [y / Enter] Confirm [n / Esc] Cancel' })] }));
|
|
15
7
|
}
|
|
@@ -3,5 +3,5 @@ type HelpOverlayProps = {
|
|
|
3
3
|
mode: 'analyze' | 'validate' | 'review';
|
|
4
4
|
onClose: () => void;
|
|
5
5
|
};
|
|
6
|
-
export declare function HelpOverlay({ mode, onClose }: HelpOverlayProps): React.ReactElement;
|
|
6
|
+
export declare function HelpOverlay({ mode, onClose: _onClose }: HelpOverlayProps): React.ReactElement;
|
|
7
7
|
export {};
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
|
|
4
|
-
export function HelpOverlay({ mode, onClose }) {
|
|
5
|
-
useImmediateInput((input, key) => {
|
|
6
|
-
if (input === '?' || key.escape) {
|
|
7
|
-
onClose();
|
|
8
|
-
}
|
|
9
|
-
});
|
|
3
|
+
// Pure render — input handled by inputToAction in App.
|
|
4
|
+
export function HelpOverlay({ mode, onClose: _onClose }) {
|
|
10
5
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", padding: 1, width: 46, children: [_jsx(Text, { bold: true, children: '─'.repeat(18) + ' Help ' + '─'.repeat(18) }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Navigation" }), _jsx(Text, { children: ' ↑ / k / PgUp Scroll up' }), _jsx(Text, { children: ' ↓ / j / PgDn Scroll down' }), _jsx(Text, { children: ' g / Home Jump to top' }), _jsx(Text, { children: ' G / End Jump to bottom' }), mode === 'review' && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "[Review mode only]" }), _jsx(Text, { children: ' Tab Toggle sidebar/detail' }), _jsx(Text, { children: ' a Accept component' }), _jsx(Text, { children: ' r Reject component' }), _jsx(Text, { children: ' e Edit proposal' }), _jsx(Text, { children: ' s Toggle source code' }), _jsx(Text, { children: ' A Approve all' }), _jsx(Text, { children: ' F Open finalize dialog' }), _jsx(Text, { children: ' Ctrl+S Save draft' }), _jsx(Text, { children: ' Ctrl+Z Undo' }), _jsx(Text, { children: ' Esc Exit edit / close' })] })), _jsx(Text, { children: " " }), _jsx(Text, { children: ' ? Close help' }), _jsx(Text, { children: ' q Quit' })] }));
|
|
11
6
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import type { EditorState } from '../state.js';
|
|
2
3
|
type JsonEditorProps = {
|
|
3
|
-
|
|
4
|
+
editorState: EditorState;
|
|
4
5
|
width: number;
|
|
5
6
|
height: number;
|
|
6
|
-
onChange: (value: string) => void;
|
|
7
|
-
onSave: () => void;
|
|
8
|
-
onDiscard: () => void;
|
|
9
7
|
};
|
|
10
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Pure render component — no input handling, no hooks.
|
|
10
|
+
* All state lives in the reducer; all keys are handled by inputToAction.
|
|
11
|
+
*/
|
|
12
|
+
export declare function JsonEditor({ editorState, width, height }: JsonEditorProps): React.ReactElement;
|
|
11
13
|
export {};
|
|
@@ -1,154 +1,23 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
2
|
import { Box, Text } from 'ink';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
});
|
|
12
|
-
const [scrollRow, setScrollRow] = useState(0);
|
|
13
|
-
const [scrollCol] = useState(0);
|
|
14
|
-
const [validationError, setValidationError] = useState(null);
|
|
15
|
-
const [cursorVisible, setCursorVisible] = useState(true);
|
|
16
|
-
// Blinking cursor
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
const interval = setInterval(() => setCursorVisible((v) => !v), 500);
|
|
19
|
-
return () => clearInterval(interval);
|
|
20
|
-
}, []);
|
|
21
|
-
const { lines, cursorRow, cursorCol } = undo.current;
|
|
22
|
-
// Keep scroll in sync with cursor
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
if (cursorRow < scrollRow)
|
|
25
|
-
setScrollRow(cursorRow);
|
|
26
|
-
if (cursorRow >= scrollRow + height)
|
|
27
|
-
setScrollRow(cursorRow - height + 1);
|
|
28
|
-
}, [cursorRow, scrollRow, height]);
|
|
29
|
-
useImmediateInput((input, key) => {
|
|
30
|
-
const currentLines = undo.current.lines;
|
|
31
|
-
const currentRow = undo.current.cursorRow;
|
|
32
|
-
const currentCol = undo.current.cursorCol;
|
|
33
|
-
if (key.ctrl && input === 's') {
|
|
34
|
-
// Validate and save
|
|
35
|
-
const text = currentLines.join('\n');
|
|
36
|
-
try {
|
|
37
|
-
JSON.parse(text);
|
|
38
|
-
setValidationError(null);
|
|
39
|
-
onChange(text);
|
|
40
|
-
onSave();
|
|
41
|
-
}
|
|
42
|
-
catch (e) {
|
|
43
|
-
setValidationError(e instanceof Error ? e.message : String(e));
|
|
44
|
-
}
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
if (key.escape) {
|
|
48
|
-
onDiscard();
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if (key.ctrl && input === 'z') {
|
|
52
|
-
undo.undo();
|
|
53
|
-
onChange(undo.current.lines.join('\n'));
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
let newLines = [...currentLines];
|
|
57
|
-
let newRow = currentRow;
|
|
58
|
-
let newCol = currentCol;
|
|
59
|
-
if (key.return) {
|
|
60
|
-
const before = newLines[currentRow].slice(0, currentCol);
|
|
61
|
-
const after = newLines[currentRow].slice(currentCol);
|
|
62
|
-
newLines = [...newLines.slice(0, currentRow), before, after, ...newLines.slice(currentRow + 1)];
|
|
63
|
-
newRow = currentRow + 1;
|
|
64
|
-
newCol = 0;
|
|
65
|
-
}
|
|
66
|
-
else if (key.backspace || key.delete) {
|
|
67
|
-
if (key.backspace) {
|
|
68
|
-
if (currentCol > 0) {
|
|
69
|
-
newLines[currentRow] = newLines[currentRow].slice(0, currentCol - 1) + newLines[currentRow].slice(currentCol);
|
|
70
|
-
newCol = currentCol - 1;
|
|
71
|
-
}
|
|
72
|
-
else if (currentRow > 0) {
|
|
73
|
-
const prevLen = newLines[currentRow - 1].length;
|
|
74
|
-
newLines[currentRow - 1] = newLines[currentRow - 1] + newLines[currentRow];
|
|
75
|
-
newLines = [...newLines.slice(0, currentRow), ...newLines.slice(currentRow + 1)];
|
|
76
|
-
newRow = currentRow - 1;
|
|
77
|
-
newCol = prevLen;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
// Delete key
|
|
82
|
-
if (currentCol < newLines[currentRow].length) {
|
|
83
|
-
newLines[currentRow] = newLines[currentRow].slice(0, currentCol) + newLines[currentRow].slice(currentCol + 1);
|
|
84
|
-
}
|
|
85
|
-
else if (currentRow < newLines.length - 1) {
|
|
86
|
-
newLines[currentRow] = newLines[currentRow] + newLines[currentRow + 1];
|
|
87
|
-
newLines = [...newLines.slice(0, currentRow + 1), ...newLines.slice(currentRow + 2)];
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
else if (key.leftArrow) {
|
|
92
|
-
if (currentCol > 0) {
|
|
93
|
-
newCol = currentCol - 1;
|
|
94
|
-
}
|
|
95
|
-
else if (currentRow > 0) {
|
|
96
|
-
newRow = currentRow - 1;
|
|
97
|
-
newCol = newLines[currentRow - 1].length;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
else if (key.rightArrow) {
|
|
101
|
-
if (currentCol < newLines[currentRow].length) {
|
|
102
|
-
newCol = currentCol + 1;
|
|
103
|
-
}
|
|
104
|
-
else if (currentRow < newLines.length - 1) {
|
|
105
|
-
newRow = currentRow + 1;
|
|
106
|
-
newCol = 0;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
else if (key.upArrow) {
|
|
110
|
-
if (currentRow > 0) {
|
|
111
|
-
newRow = currentRow - 1;
|
|
112
|
-
newCol = Math.min(currentCol, newLines[currentRow - 1].length);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
else if (key.downArrow) {
|
|
116
|
-
if (currentRow < newLines.length - 1) {
|
|
117
|
-
newRow = currentRow + 1;
|
|
118
|
-
newCol = Math.min(currentCol, newLines[currentRow + 1].length);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
else if (input === '\x1b[H' || input === '\x1b[1~') {
|
|
122
|
-
// Home key
|
|
123
|
-
newCol = 0;
|
|
124
|
-
}
|
|
125
|
-
else if (input === '\x1b[F' || input === '\x1b[4~') {
|
|
126
|
-
// End key
|
|
127
|
-
newCol = newLines[currentRow].length;
|
|
128
|
-
}
|
|
129
|
-
else if (input && input.length === 1 && !key.ctrl && !key.meta) {
|
|
130
|
-
// Printable character
|
|
131
|
-
newLines[currentRow] = newLines[currentRow].slice(0, currentCol) + input + newLines[currentRow].slice(currentCol);
|
|
132
|
-
newCol = currentCol + 1;
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
return; // No change
|
|
136
|
-
}
|
|
137
|
-
undo.push({ lines: newLines, cursorRow: newRow, cursorCol: newCol });
|
|
138
|
-
onChange(newLines.join('\n'));
|
|
139
|
-
});
|
|
3
|
+
/**
|
|
4
|
+
* Pure render component — no input handling, no hooks.
|
|
5
|
+
* All state lives in the reducer; all keys are handled by inputToAction.
|
|
6
|
+
*/
|
|
7
|
+
export function JsonEditor({ editorState, width, height }) {
|
|
8
|
+
const { cursor, scrollRow, validationError } = editorState;
|
|
9
|
+
const { lines, cursorRow, cursorCol } = cursor;
|
|
140
10
|
const innerWidth = Math.max(1, width - 2);
|
|
141
11
|
const visibleLines = lines.slice(scrollRow, scrollRow + height);
|
|
142
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, borderStyle: "single", borderColor: "white", children: [_jsx(Text, { bold: true, children: 'EDIT
|
|
12
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, borderStyle: "single", borderColor: "white", children: [_jsx(Text, { bold: true, children: 'EDIT [Ctrl+S save · Ctrl+Z undo · Esc discard]' }), visibleLines.map((line, displayRow) => {
|
|
143
13
|
const actualRow = displayRow + scrollRow;
|
|
144
|
-
const displayLine = line.slice(
|
|
14
|
+
const displayLine = line.slice(0, innerWidth);
|
|
145
15
|
if (actualRow === cursorRow) {
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
return (_jsxs(Box, { children: [_jsx(Text, { children: beforeCursor }), _jsx(Text, { inverse: cursorVisible, children: cursorChar }), _jsx(Text, { children: afterCursor })] }, displayRow));
|
|
16
|
+
const before = displayLine.slice(0, cursorCol);
|
|
17
|
+
const cursorChar = displayLine[cursorCol] ?? ' ';
|
|
18
|
+
const after = displayLine.slice(cursorCol + 1);
|
|
19
|
+
return (_jsxs(Box, { children: [_jsx(Text, { children: before }), _jsx(Text, { inverse: true, children: cursorChar }), _jsx(Text, { children: after })] }, actualRow));
|
|
151
20
|
}
|
|
152
|
-
return _jsx(Text, { children: displayLine },
|
|
153
|
-
}), validationError && _jsx(Text, { color: "red", children: '✗
|
|
21
|
+
return _jsx(Text, { children: displayLine }, actualRow);
|
|
22
|
+
}), validationError && _jsx(Text, { color: "red", children: '✗ ' + validationError })] }));
|
|
154
23
|
}
|
|
@@ -4,5 +4,5 @@ type QuitDialogProps = {
|
|
|
4
4
|
onConfirm: () => void;
|
|
5
5
|
onCancel: () => void;
|
|
6
6
|
};
|
|
7
|
-
export declare function QuitDialog({ hasUnsavedDrafts, onConfirm, onCancel }: QuitDialogProps): React.ReactElement;
|
|
7
|
+
export declare function QuitDialog({ hasUnsavedDrafts, onConfirm: _onConfirm, onCancel: _onCancel, }: QuitDialogProps): React.ReactElement;
|
|
8
8
|
export {};
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
|
|
4
|
-
export function QuitDialog({ hasUnsavedDrafts, onConfirm, onCancel }) {
|
|
5
|
-
useImmediateInput((input, key) => {
|
|
6
|
-
if (input === 'y' || key.return) {
|
|
7
|
-
onConfirm();
|
|
8
|
-
}
|
|
9
|
-
else if (input === 'n' || key.escape) {
|
|
10
|
-
onCancel();
|
|
11
|
-
}
|
|
12
|
-
});
|
|
3
|
+
// Pure render — input handled by inputToAction in App.
|
|
4
|
+
export function QuitDialog({ hasUnsavedDrafts, onConfirm: _onConfirm, onCancel: _onCancel, }) {
|
|
13
5
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", padding: 1, width: 50, children: [_jsx(Text, { bold: true, children: '─'.repeat(19) + ' Quit ' + '─'.repeat(19) }), _jsx(Text, { children: " " }), hasUnsavedDrafts ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: "You have unsaved draft edits." }), _jsx(Text, { children: 'Session state is preserved — you can resume' }), _jsx(Text, { children: 'by running the same review command again.' })] })) : (_jsx(Text, { children: "Session is saved. Quit without finalizing?" })), _jsx(Text, { children: " " }), _jsx(Text, { children: ' [y / Enter] Quit [n / Esc] Stay' })] }));
|
|
14
6
|
}
|
|
@@ -4,8 +4,6 @@ type StatusBarProps = {
|
|
|
4
4
|
rejected: number;
|
|
5
5
|
reviewed: number;
|
|
6
6
|
needsReview: number;
|
|
7
|
-
onApproveAll: () => void;
|
|
8
|
-
onFinalize: () => void;
|
|
9
7
|
};
|
|
10
8
|
export declare function StatusBar({ accepted, rejected, reviewed, needsReview }: StatusBarProps): React.ReactElement;
|
|
11
9
|
export {};
|
|
@@ -19,6 +19,11 @@ type InputHandler = (input: string, key: Key) => void;
|
|
|
19
19
|
* Like Ink's useInput, but uses useLayoutEffect so the listener is registered
|
|
20
20
|
* synchronously after render. This allows stdin.write() calls in tests to work
|
|
21
21
|
* immediately after render() without awaiting effects.
|
|
22
|
+
*
|
|
23
|
+
* Raw mode is NOT toggled here — it is managed once at the App level via
|
|
24
|
+
* useRawMode. Toggling setRawMode per-hook caused brief raw-mode drops during
|
|
25
|
+
* re-renders (e.g. when FieldEditor mounted alongside useKeymap), blanking the
|
|
26
|
+
* terminal.
|
|
22
27
|
*/
|
|
23
28
|
export declare function useImmediateInput(handler: InputHandler): void;
|
|
24
29
|
export {};
|
|
@@ -47,13 +47,17 @@ function parseInput(data) {
|
|
|
47
47
|
* Like Ink's useInput, but uses useLayoutEffect so the listener is registered
|
|
48
48
|
* synchronously after render. This allows stdin.write() calls in tests to work
|
|
49
49
|
* immediately after render() without awaiting effects.
|
|
50
|
+
*
|
|
51
|
+
* Raw mode is NOT toggled here — it is managed once at the App level via
|
|
52
|
+
* useRawMode. Toggling setRawMode per-hook caused brief raw-mode drops during
|
|
53
|
+
* re-renders (e.g. when FieldEditor mounted alongside useKeymap), blanking the
|
|
54
|
+
* terminal.
|
|
50
55
|
*/
|
|
51
56
|
export function useImmediateInput(handler) {
|
|
52
|
-
const { stdin
|
|
57
|
+
const { stdin } = useStdin();
|
|
53
58
|
const handlerRef = useRef(handler);
|
|
54
59
|
handlerRef.current = handler;
|
|
55
60
|
useLayoutEffect(() => {
|
|
56
|
-
setRawMode(true);
|
|
57
61
|
const handleData = (data) => {
|
|
58
62
|
const str = Buffer.isBuffer(data) ? data.toString('utf8') : data;
|
|
59
63
|
const { input, key } = parseInput(str);
|
|
@@ -62,7 +66,6 @@ export function useImmediateInput(handler) {
|
|
|
62
66
|
stdin.on('data', handleData);
|
|
63
67
|
return () => {
|
|
64
68
|
stdin.off('data', handleData);
|
|
65
|
-
setRawMode(false);
|
|
66
69
|
};
|
|
67
|
-
}, [
|
|
70
|
+
}, []); // Register once on mount. Handler is always fresh via handlerRef — no re-registration needed.
|
|
68
71
|
}
|
|
@@ -1,24 +1 @@
|
|
|
1
|
-
type KeymapContext = {
|
|
2
|
-
sidebarFocused: boolean;
|
|
3
|
-
editMode: boolean;
|
|
4
|
-
dialogOpen: boolean;
|
|
5
|
-
disabled: boolean;
|
|
6
|
-
};
|
|
7
|
-
type KeymapHandlers = {
|
|
8
|
-
onSidebarUp: () => void;
|
|
9
|
-
onSidebarDown: () => void;
|
|
10
|
-
onSidebarSelect: () => void;
|
|
11
|
-
onAccept: () => void;
|
|
12
|
-
onReject: () => void;
|
|
13
|
-
onEnterEditMode: () => void;
|
|
14
|
-
onToggleSource: () => void;
|
|
15
|
-
onScrollUp: () => void;
|
|
16
|
-
onScrollDown: () => void;
|
|
17
|
-
onToggleFocus: () => void;
|
|
18
|
-
onApproveAll: () => void;
|
|
19
|
-
onFinalize: () => void;
|
|
20
|
-
onQuit: () => void;
|
|
21
|
-
onToggleHelp: () => void;
|
|
22
|
-
};
|
|
23
|
-
export declare function useKeymap(context: KeymapContext, handlers: KeymapHandlers): void;
|
|
24
1
|
export {};
|
|
@@ -1,67 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export function useKeymap(context, handlers) {
|
|
3
|
-
useImmediateInput((input, key) => {
|
|
4
|
-
if (context.disabled)
|
|
5
|
-
return;
|
|
6
|
-
if (context.dialogOpen)
|
|
7
|
-
return;
|
|
8
|
-
if (context.editMode)
|
|
9
|
-
return;
|
|
10
|
-
if (input === 'q') {
|
|
11
|
-
handlers.onQuit();
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
if (input === '?') {
|
|
15
|
-
handlers.onToggleHelp();
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
if (key.tab) {
|
|
19
|
-
handlers.onToggleFocus();
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
if (input === 'A') {
|
|
23
|
-
handlers.onApproveAll();
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
if (input === 'F') {
|
|
27
|
-
handlers.onFinalize();
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
// a/r/e/s work regardless of sidebar focus
|
|
31
|
-
if (input === 'a') {
|
|
32
|
-
handlers.onAccept();
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
if (input === 'r') {
|
|
36
|
-
handlers.onReject();
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
if (input === 'e') {
|
|
40
|
-
handlers.onEnterEditMode();
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (input === 's') {
|
|
44
|
-
handlers.onToggleSource();
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
if (context.sidebarFocused) {
|
|
48
|
-
if (key.upArrow || input === 'k') {
|
|
49
|
-
handlers.onSidebarUp();
|
|
50
|
-
}
|
|
51
|
-
else if (key.downArrow || input === 'j') {
|
|
52
|
-
handlers.onSidebarDown();
|
|
53
|
-
}
|
|
54
|
-
else if (key.return) {
|
|
55
|
-
handlers.onSidebarSelect();
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
if (key.upArrow || input === 'k') {
|
|
60
|
-
handlers.onScrollUp();
|
|
61
|
-
}
|
|
62
|
-
else if (key.downArrow || input === 'j') {
|
|
63
|
-
handlers.onScrollDown();
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
}
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useLayoutEffect } from 'react';
|
|
2
|
+
import { useStdin } from 'ink';
|
|
3
|
+
/**
|
|
4
|
+
* Enables raw terminal mode for the lifetime of the component.
|
|
5
|
+
* Call once at the root of each top-level TUI component.
|
|
6
|
+
* useImmediateInput does NOT manage raw mode — this hook does.
|
|
7
|
+
*/
|
|
8
|
+
export function useRawMode() {
|
|
9
|
+
const { setRawMode } = useStdin();
|
|
10
|
+
useLayoutEffect(() => {
|
|
11
|
+
setRawMode(true);
|
|
12
|
+
return () => {
|
|
13
|
+
setRawMode(false);
|
|
14
|
+
};
|
|
15
|
+
}, [setRawMode]);
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AppState, AppAction } from '../state.js';
|
|
2
|
+
import type { ReviewSessionSnapshot } from '../../types.js';
|
|
3
|
+
type Services = {
|
|
4
|
+
sessionId: string;
|
|
5
|
+
saveState: (session: ReviewSessionSnapshot) => Promise<void>;
|
|
6
|
+
appendEvent: (event: {
|
|
7
|
+
type: string;
|
|
8
|
+
payload: Record<string, unknown>;
|
|
9
|
+
}) => Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
type Dispatch = (action: AppAction) => void;
|
|
12
|
+
/**
|
|
13
|
+
* All async side effects in one place.
|
|
14
|
+
* Detects transitions by comparing state to prevState — no pending* counter signals needed.
|
|
15
|
+
*/
|
|
16
|
+
export declare function useSideEffects(state: AppState, dispatch: Dispatch, services: Services): void;
|
|
17
|
+
export {};
|