| 120 | } |
| 121 | |
| 122 | const onData = (data: Buffer) => { |
| 123 | const key = data.toString() |
| 124 | if (key === '\x03') { |
| 125 | cleanup() |
| 126 | process.exit(0) |
| 127 | } |
| 128 | // Arrow up / k |
| 129 | if (key === '\x1b[A' || key === 'k') { |
| 130 | selected = (selected - 1 + choices.length) % choices.length |
| 131 | render() |
| 132 | return |
| 133 | } |
| 134 | // Arrow down / j |
| 135 | if (key === '\x1b[B' || key === 'j') { |
| 136 | selected = (selected + 1) % choices.length |
| 137 | render() |
| 138 | return |
| 139 | } |
| 140 | // Enter — confirm current selection |
| 141 | if (key === '\r' || key === '\n') { |
| 142 | cleanup() |
| 143 | process.stderr.write('\n') |
| 144 | resolve(selected + 1) |
| 145 | return |
| 146 | } |
| 147 | // Digit key — direct choice |
| 148 | if (key.length === 1) { |
| 149 | const num = parseInt(key) |
| 150 | if (num >= 1 && num <= choices.length) { |
| 151 | cleanup() |
| 152 | process.stderr.write('\n') |
| 153 | resolve(num) |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | process.stdin.on('data', onData) |
| 158 | }) |
| 159 | } |