UNPKG

11.5 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5}) : (function(o, m, k, k2) {
6 if (k2 === undefined) k2 = k;
7 o[k2] = m[k];
8}));
9var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10 Object.defineProperty(o, "default", { enumerable: true, value: v });
11}) : function(o, v) {
12 o["default"] = v;
13});
14var __importStar = (this && this.__importStar) || function (mod) {
15 if (mod && mod.__esModule) return mod;
16 var result = {};
17 if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18 __setModuleDefault(result, mod);
19 return result;
20};
21var __importDefault = (this && this.__importDefault) || function (mod) {
22 return (mod && mod.__esModule) ? mod : { "default": mod };
23};
24Object.defineProperty(exports, "__esModule", { value: true });
25/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
26const react_1 = __importStar(require("react"));
27const cli_cursor_1 = __importDefault(require("cli-cursor"));
28const AppContext_1 = __importDefault(require("./AppContext"));
29const StdinContext_1 = __importDefault(require("./StdinContext"));
30const StdoutContext_1 = __importDefault(require("./StdoutContext"));
31const StderrContext_1 = __importDefault(require("./StderrContext"));
32const FocusContext_1 = __importDefault(require("./FocusContext"));
33const ErrorOverview_1 = __importDefault(require("./ErrorOverview"));
34const TAB = '\t';
35const SHIFT_TAB = '\u001B[Z';
36const ESC = '\u001B';
37// Root component for all Ink apps
38// It renders stdin and stdout contexts, so that children can access them if needed
39// It also handles Ctrl+C exiting and cursor visibility
40class App extends react_1.PureComponent {
41 constructor() {
42 super(...arguments);
43 this.state = {
44 isFocusEnabled: true,
45 activeFocusId: undefined,
46 focusables: [],
47 error: undefined
48 };
49 // Count how many components enabled raw mode to avoid disabling
50 // raw mode until all components don't need it anymore
51 this.rawModeEnabledCount = 0;
52 this.handleSetRawMode = (isEnabled) => {
53 const { stdin } = this.props;
54 if (!this.isRawModeSupported()) {
55 if (stdin === process.stdin) {
56 throw new Error('Raw mode is not supported on the current process.stdin, which Ink uses as input stream by default.\nRead about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported');
57 }
58 else {
59 throw new Error('Raw mode is not supported on the stdin provided to Ink.\nRead about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported');
60 }
61 }
62 stdin.setEncoding('utf8');
63 if (isEnabled) {
64 // Ensure raw mode is enabled only once
65 if (this.rawModeEnabledCount === 0) {
66 stdin.addListener('data', this.handleInput);
67 stdin.resume();
68 stdin.setRawMode(true);
69 }
70 this.rawModeEnabledCount++;
71 return;
72 }
73 // Disable raw mode only when no components left that are using it
74 if (--this.rawModeEnabledCount === 0) {
75 stdin.setRawMode(false);
76 stdin.removeListener('data', this.handleInput);
77 stdin.pause();
78 }
79 };
80 this.handleInput = (input) => {
81 // Exit on Ctrl+C
82 // eslint-disable-next-line unicorn/no-hex-escape
83 if (input === '\x03' && this.props.exitOnCtrlC) {
84 this.handleExit();
85 }
86 // Reset focus when there's an active focused component on Esc
87 if (input === ESC && this.state.activeFocusId) {
88 this.setState({
89 activeFocusId: undefined
90 });
91 }
92 if (this.state.isFocusEnabled && this.state.focusables.length > 0) {
93 if (input === TAB) {
94 this.focusNext();
95 }
96 if (input === SHIFT_TAB) {
97 this.focusPrevious();
98 }
99 }
100 };
101 this.handleExit = (error) => {
102 if (this.isRawModeSupported()) {
103 this.handleSetRawMode(false);
104 }
105 this.props.onExit(error);
106 };
107 this.enableFocus = () => {
108 this.setState({
109 isFocusEnabled: true
110 });
111 };
112 this.disableFocus = () => {
113 this.setState({
114 isFocusEnabled: false
115 });
116 };
117 this.focusNext = () => {
118 this.setState(previousState => {
119 var _a;
120 const firstFocusableId = (_a = previousState.focusables[0]) === null || _a === void 0 ? void 0 : _a.id;
121 const nextFocusableId = this.findNextFocusable(previousState);
122 return {
123 activeFocusId: nextFocusableId || firstFocusableId
124 };
125 });
126 };
127 this.focusPrevious = () => {
128 this.setState(previousState => {
129 var _a;
130 const lastFocusableId = (_a = previousState.focusables[previousState.focusables.length - 1]) === null || _a === void 0 ? void 0 : _a.id;
131 const previousFocusableId = this.findPreviousFocusable(previousState);
132 return {
133 activeFocusId: previousFocusableId || lastFocusableId
134 };
135 });
136 };
137 this.addFocusable = (id, { autoFocus }) => {
138 this.setState(previousState => {
139 let nextFocusId = previousState.activeFocusId;
140 if (!nextFocusId && autoFocus) {
141 nextFocusId = id;
142 }
143 return {
144 activeFocusId: nextFocusId,
145 focusables: [
146 ...previousState.focusables,
147 {
148 id,
149 isActive: true
150 }
151 ]
152 };
153 });
154 };
155 this.removeFocusable = (id) => {
156 this.setState(previousState => ({
157 activeFocusId: previousState.activeFocusId === id
158 ? undefined
159 : previousState.activeFocusId,
160 focusables: previousState.focusables.filter(focusable => {
161 return focusable.id !== id;
162 })
163 }));
164 };
165 this.activateFocusable = (id) => {
166 this.setState(previousState => ({
167 focusables: previousState.focusables.map(focusable => {
168 if (focusable.id !== id) {
169 return focusable;
170 }
171 return {
172 id,
173 isActive: true
174 };
175 })
176 }));
177 };
178 this.deactivateFocusable = (id) => {
179 this.setState(previousState => ({
180 activeFocusId: previousState.activeFocusId === id
181 ? undefined
182 : previousState.activeFocusId,
183 focusables: previousState.focusables.map(focusable => {
184 if (focusable.id !== id) {
185 return focusable;
186 }
187 return {
188 id,
189 isActive: false
190 };
191 })
192 }));
193 };
194 this.findNextFocusable = (state) => {
195 var _a;
196 const activeIndex = state.focusables.findIndex(focusable => {
197 return focusable.id === state.activeFocusId;
198 });
199 for (let index = activeIndex + 1; index < state.focusables.length; index++) {
200 if ((_a = state.focusables[index]) === null || _a === void 0 ? void 0 : _a.isActive) {
201 return state.focusables[index].id;
202 }
203 }
204 return undefined;
205 };
206 this.findPreviousFocusable = (state) => {
207 var _a;
208 const activeIndex = state.focusables.findIndex(focusable => {
209 return focusable.id === state.activeFocusId;
210 });
211 for (let index = activeIndex - 1; index >= 0; index--) {
212 if ((_a = state.focusables[index]) === null || _a === void 0 ? void 0 : _a.isActive) {
213 return state.focusables[index].id;
214 }
215 }
216 return undefined;
217 };
218 }
219 static getDerivedStateFromError(error) {
220 return { error };
221 }
222 // Determines if TTY is supported on the provided stdin
223 isRawModeSupported() {
224 return this.props.stdin.isTTY;
225 }
226 render() {
227 return (react_1.default.createElement(AppContext_1.default.Provider, { value: {
228 exit: this.handleExit
229 } },
230 react_1.default.createElement(StdinContext_1.default.Provider, { value: {
231 stdin: this.props.stdin,
232 setRawMode: this.handleSetRawMode,
233 isRawModeSupported: this.isRawModeSupported(),
234 internal_exitOnCtrlC: this.props.exitOnCtrlC
235 } },
236 react_1.default.createElement(StdoutContext_1.default.Provider, { value: {
237 stdout: this.props.stdout,
238 write: this.props.writeToStdout
239 } },
240 react_1.default.createElement(StderrContext_1.default.Provider, { value: {
241 stderr: this.props.stderr,
242 write: this.props.writeToStderr
243 } },
244 react_1.default.createElement(FocusContext_1.default.Provider, { value: {
245 activeId: this.state.activeFocusId,
246 add: this.addFocusable,
247 remove: this.removeFocusable,
248 activate: this.activateFocusable,
249 deactivate: this.deactivateFocusable,
250 enableFocus: this.enableFocus,
251 disableFocus: this.disableFocus,
252 focusNext: this.focusNext,
253 focusPrevious: this.focusPrevious
254 } }, this.state.error ? (react_1.default.createElement(ErrorOverview_1.default, { error: this.state.error })) : (this.props.children)))))));
255 }
256 componentDidMount() {
257 cli_cursor_1.default.hide(this.props.stdout);
258 }
259 componentWillUnmount() {
260 cli_cursor_1.default.show(this.props.stdout);
261 // ignore calling setRawMode on an handle stdin it cannot be called
262 if (this.isRawModeSupported()) {
263 this.handleSetRawMode(false);
264 }
265 }
266 componentDidCatch(error) {
267 this.handleExit(error);
268 }
269}
270exports.default = App;
271App.displayName = 'InternalApp';
272//# sourceMappingURL=App.js.map
\No newline at end of file