bare-tui 0.0.0 → 0.0.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.
- package/LICENSE +201 -0
- package/NOTICE +13 -0
- package/README.md +261 -0
- package/ansi.js +34 -0
- package/commands.js +67 -0
- package/components/autocomplete.js +208 -0
- package/components/checkbox.js +68 -0
- package/components/filepicker.js +232 -0
- package/components/focus.js +129 -0
- package/components/help.js +117 -0
- package/components/list.js +172 -0
- package/components/paginator.js +111 -0
- package/components/progress.js +78 -0
- package/components/radio.js +113 -0
- package/components/select.js +165 -0
- package/components/spinner.js +70 -0
- package/components/stopwatch.js +88 -0
- package/components/table.js +130 -0
- package/components/textarea.js +279 -0
- package/components/textinput.js +135 -0
- package/components/timer.js +91 -0
- package/components/viewport.js +104 -0
- package/docs/autocomplete.md +80 -0
- package/docs/checkbox.md +43 -0
- package/docs/filepicker.md +80 -0
- package/docs/focus.md +68 -0
- package/docs/help.md +45 -0
- package/docs/list.md +47 -0
- package/docs/paginator.md +43 -0
- package/docs/progress.md +36 -0
- package/docs/radio.md +49 -0
- package/docs/select.md +54 -0
- package/docs/spinner.md +54 -0
- package/docs/stopwatch.md +52 -0
- package/docs/table.md +52 -0
- package/docs/textarea.md +43 -0
- package/docs/textinput.md +51 -0
- package/docs/timer.md +57 -0
- package/docs/viewport.md +39 -0
- package/index.d.ts +1 -0
- package/index.js +71 -0
- package/key.js +29 -0
- package/messages.js +62 -0
- package/mouse.js +102 -0
- package/package.json +59 -1
- package/program.js +387 -0
- package/renderer.js +67 -0
- package/style.js +473 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# textinput
|
|
2
|
+
|
|
3
|
+
A single-line editable text field. Input-driven: no commands, just state folded
|
|
4
|
+
from key messages. Only consumes keys while focused, so a parent can host several
|
|
5
|
+
and route to whichever has focus.
|
|
6
|
+
|
|
7
|
+
[← all components](../README.md#components)
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
const { textinput } = require('bare-tui')
|
|
13
|
+
|
|
14
|
+
class Form {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.name = textinput.create({ placeholder: 'name', prompt: '> ' }).focus()
|
|
17
|
+
}
|
|
18
|
+
update(msg) {
|
|
19
|
+
const [f, cmd] = this.name.update(msg)
|
|
20
|
+
this.name = f
|
|
21
|
+
return [this, cmd]
|
|
22
|
+
}
|
|
23
|
+
view() {
|
|
24
|
+
return this.name.view()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Options
|
|
30
|
+
|
|
31
|
+
| Option | Default | Description |
|
|
32
|
+
| ------------- | ---------- | ------------------------------------------- |
|
|
33
|
+
| `value` | `''` | Initial text |
|
|
34
|
+
| `placeholder` | `''` | Dim text shown when empty |
|
|
35
|
+
| `prompt` | `''` | Prefix drawn before the value (e.g. `'> '`) |
|
|
36
|
+
| `charLimit` | `0` | Max length (`0` = unlimited) |
|
|
37
|
+
| `echoMode` | `'normal'` | `'password'` masks the value |
|
|
38
|
+
| `maskChar` | `'•'` | Mask character in password mode |
|
|
39
|
+
| `focused` | `false` | Start focused |
|
|
40
|
+
|
|
41
|
+
## API
|
|
42
|
+
|
|
43
|
+
- `focus()` / `blur()` — toggle whether keys are consumed.
|
|
44
|
+
- `setValue(v)` / `reset()` — set or clear the text.
|
|
45
|
+
- `.value` — the current string.
|
|
46
|
+
|
|
47
|
+
## Keys
|
|
48
|
+
|
|
49
|
+
`←`/`→` move, `home`/`end` (and `ctrl+a`/`ctrl+e`) jump, `backspace`/`delete`
|
|
50
|
+
edit, printable characters insert at the cursor. The field draws its own
|
|
51
|
+
reverse-video cursor (the Program hides the real one).
|
package/docs/timer.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# timer
|
|
2
|
+
|
|
3
|
+
Counts a duration down to zero. Command-driven like the
|
|
4
|
+
[stopwatch](stopwatch.md); when it reaches zero it stops and emits a one-shot
|
|
5
|
+
timeout message.
|
|
6
|
+
|
|
7
|
+
[← all components](../README.md#components)
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
const { timer } = require('bare-tui')
|
|
13
|
+
|
|
14
|
+
class App {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.timer = timer.create({ timeout: 10000 }) // 10s
|
|
17
|
+
}
|
|
18
|
+
init() {
|
|
19
|
+
return this.timer.start()
|
|
20
|
+
}
|
|
21
|
+
update(msg) {
|
|
22
|
+
if (msg.type === 'timer.timeout') {
|
|
23
|
+
/* done */ return [this, null]
|
|
24
|
+
}
|
|
25
|
+
if (msg.type === 'timer.tick') {
|
|
26
|
+
const [t, cmd] = this.timer.update(msg)
|
|
27
|
+
this.timer = t
|
|
28
|
+
return [this, cmd]
|
|
29
|
+
}
|
|
30
|
+
return [this, null]
|
|
31
|
+
}
|
|
32
|
+
view() {
|
|
33
|
+
return this.timer.view() // "00:09"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Options
|
|
39
|
+
|
|
40
|
+
| Option | Default | Description |
|
|
41
|
+
| ---------- | ------- | -------------------------------- |
|
|
42
|
+
| `timeout` | `0` | Initial duration in milliseconds |
|
|
43
|
+
| `interval` | `1000` | Milliseconds per tick |
|
|
44
|
+
|
|
45
|
+
## API
|
|
46
|
+
|
|
47
|
+
- `start()` / `stop()` / `toggle()` — return a command (or `null`). Won't start
|
|
48
|
+
from zero.
|
|
49
|
+
- `reset()` — restore the initial duration.
|
|
50
|
+
- `.timeout` (ms remaining) / `.running` / `.timedOut`.
|
|
51
|
+
- `view()` → `MM:SS`.
|
|
52
|
+
|
|
53
|
+
## Messages
|
|
54
|
+
|
|
55
|
+
Consumes `{ type: 'timer.tick', id, tag }`; on reaching zero its `update` returns
|
|
56
|
+
a command that emits `{ type: 'timer.timeout', id }` — handle that to react to
|
|
57
|
+
the timeout.
|
package/docs/viewport.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# viewport
|
|
2
|
+
|
|
3
|
+
A scrollable window over content taller than the available space. Renders a
|
|
4
|
+
fixed `height`-row window and always emits exactly that many lines (padding short
|
|
5
|
+
content), so surrounding layout stays stable.
|
|
6
|
+
|
|
7
|
+
[← all components](../README.md#components)
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
const { viewport } = require('bare-tui')
|
|
13
|
+
|
|
14
|
+
const vp = viewport.create({ width: 40, height: 10 })
|
|
15
|
+
vp.setContent(longText)
|
|
16
|
+
|
|
17
|
+
// in update: const [v] = vp.update(msg); this.vp = v
|
|
18
|
+
// in view: this.vp.view()
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Options
|
|
22
|
+
|
|
23
|
+
| Option | Default | Description |
|
|
24
|
+
| -------- | ------- | -------------------------------------------------- |
|
|
25
|
+
| `width` | `0` | Truncate lines to this width (`0` = no truncation) |
|
|
26
|
+
| `height` | `0` | Visible rows |
|
|
27
|
+
|
|
28
|
+
## API
|
|
29
|
+
|
|
30
|
+
- `setContent(string)` — set the scrollable text.
|
|
31
|
+
- `scrollUp(n)` / `scrollDown(n)` / `gotoTop()` / `gotoBottom()` /
|
|
32
|
+
`setYOffset(n)`.
|
|
33
|
+
- `.atTop` / `.atBottom` / `.scrollPercent` / `.maxOffset` / `.yOffset`.
|
|
34
|
+
|
|
35
|
+
## Keys
|
|
36
|
+
|
|
37
|
+
`↑`/`↓` (`k`/`j`) by line, `pgup`/`pgdn` (`b`/`f`) by page, `ctrl+u`/`ctrl+d`
|
|
38
|
+
half-page, `home`/`end` to the ends. Bindings are exported as `viewport.keys`
|
|
39
|
+
for the [help](help.md) component.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module '@holepunchto/bare-tui'
|
package/index.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// tea — a tiny Elm Architecture runtime for Bare terminals.
|
|
2
|
+
//
|
|
3
|
+
// Built on Bare's native primitives (bare-tty for raw IO + window size,
|
|
4
|
+
// bare-ansi-escapes for key decoding and escape sequences), so it runs
|
|
5
|
+
// anywhere Bare runs and pulls in no Node-only dependencies. The API is shaped
|
|
6
|
+
// after Charm's Bubble Tea (Model/Cmd/Msg/Program) with the intent of growing
|
|
7
|
+
// into a drop-out framework.
|
|
8
|
+
//
|
|
9
|
+
// const { Program, quit } = require('./lib/tea')
|
|
10
|
+
//
|
|
11
|
+
// class App {
|
|
12
|
+
// init() { return null }
|
|
13
|
+
// update(msg) {
|
|
14
|
+
// if (msg.type === 'key' && String(msg) === 'q') return [this, quit]
|
|
15
|
+
// return [this, null]
|
|
16
|
+
// }
|
|
17
|
+
// view() { return 'press q to quit' }
|
|
18
|
+
// }
|
|
19
|
+
//
|
|
20
|
+
// new Program(new App()).run()
|
|
21
|
+
const Program = require('./program')
|
|
22
|
+
const commands = require('./commands')
|
|
23
|
+
const messages = require('./messages')
|
|
24
|
+
const key = require('./key')
|
|
25
|
+
const ansi = require('./ansi')
|
|
26
|
+
const { style } = require('./style')
|
|
27
|
+
const spinner = require('./components/spinner')
|
|
28
|
+
const textinput = require('./components/textinput')
|
|
29
|
+
const autocomplete = require('./components/autocomplete')
|
|
30
|
+
const textarea = require('./components/textarea')
|
|
31
|
+
const viewport = require('./components/viewport')
|
|
32
|
+
const list = require('./components/list')
|
|
33
|
+
const table = require('./components/table')
|
|
34
|
+
const help = require('./components/help')
|
|
35
|
+
const progress = require('./components/progress')
|
|
36
|
+
const paginator = require('./components/paginator')
|
|
37
|
+
const stopwatch = require('./components/stopwatch')
|
|
38
|
+
const timer = require('./components/timer')
|
|
39
|
+
const filepicker = require('./components/filepicker')
|
|
40
|
+
const checkbox = require('./components/checkbox')
|
|
41
|
+
const radio = require('./components/radio')
|
|
42
|
+
const select = require('./components/select')
|
|
43
|
+
const focus = require('./components/focus')
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
Program,
|
|
47
|
+
...commands, // quit, batch, sequence, tick, every, suspend
|
|
48
|
+
KeyMsg: messages.KeyMsg,
|
|
49
|
+
key, // key.matches(msg, ...chords | bindings), key.binding({ keys, help })
|
|
50
|
+
ansi,
|
|
51
|
+
style, // style().bold().border(style.borders.rounded).render(...) + style.joinHorizontal/Vertical
|
|
52
|
+
|
|
53
|
+
// Components — each a composable { init?, update, view } model.
|
|
54
|
+
spinner, // spinner.create({ frames, fps })
|
|
55
|
+
textinput, // textinput.create({ placeholder, prompt, charLimit, echoMode })
|
|
56
|
+
autocomplete, // autocomplete.create({ prompt, placeholder, suggestions, trigger }) — input + suggestion menu
|
|
57
|
+
textarea, // textarea.create({ width, height, placeholder, charLimit }) — multi-line
|
|
58
|
+
viewport, // viewport.create({ width, height }) — scrollable window
|
|
59
|
+
list, // list.create({ items, height, width, title }) — selectable + filterable
|
|
60
|
+
table, // table.create({ columns, rows, height }) — selectable scrolling rows
|
|
61
|
+
help, // help.create() — renders keybinding hints; view(keymap)
|
|
62
|
+
progress, // progress.create({ width, gradient }) — view(percent)
|
|
63
|
+
paginator, // paginator.create({ perPage, total, type }) — page state + indicator
|
|
64
|
+
stopwatch, // stopwatch.create({ interval }) — counts up; start/stop/toggle
|
|
65
|
+
timer, // timer.create({ timeout, interval }) — counts down; emits timer.timeout
|
|
66
|
+
filepicker, // filepicker.create({ fs, path, cwd }) — browse + pick; filepicker.mock(tree)
|
|
67
|
+
checkbox, // checkbox.create({ label, checked }) — boolean toggle (space)
|
|
68
|
+
radio, // radio.create({ options, selected }) — single choice; value()
|
|
69
|
+
select, // select.create({ options, placeholder }) — dropdown; view() + menuView()
|
|
70
|
+
focus // focus.create({ items }) — ordered focus ring across child components
|
|
71
|
+
}
|
package/key.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Key matching helpers — the ergonomic front door to KeyMsg in update().
|
|
2
|
+
//
|
|
3
|
+
// const { key } = require('./lib/tea')
|
|
4
|
+
// if (key.matches(msg, 'q', 'ctrl+c')) return [model, quit]
|
|
5
|
+
//
|
|
6
|
+
// matches() is null/type-safe: it returns false for any non-key Msg, so you
|
|
7
|
+
// don't have to guard `msg.type === 'key'` first.
|
|
8
|
+
|
|
9
|
+
// A reusable named binding: the chords that trigger an action plus optional
|
|
10
|
+
// help text. Shaped for a future help component, but useful now as a single
|
|
11
|
+
// place to define an action's keys.
|
|
12
|
+
// const up = key.binding({ keys: ['up', 'k'], help: { key: '↑/k', desc: 'up' } })
|
|
13
|
+
// if (key.matches(msg, up)) ...
|
|
14
|
+
function binding({ keys = [], help = null } = {}) {
|
|
15
|
+
return { keys: [].concat(keys), help }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// True if `msg` is a key matching any of the given chords or bindings. Bindings
|
|
19
|
+
// are expanded to their `keys`, so you can mix:
|
|
20
|
+
// key.matches(msg, 'enter', someBinding, 'ctrl+c')
|
|
21
|
+
function matches(msg, ...items) {
|
|
22
|
+
if (!msg || msg.type !== 'key' || typeof msg.is !== 'function') return false
|
|
23
|
+
const chords = items.flatMap((item) =>
|
|
24
|
+
item && typeof item === 'object' && Array.isArray(item.keys) ? item.keys : [item]
|
|
25
|
+
)
|
|
26
|
+
return msg.is(...chords)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { matches, binding }
|
package/messages.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Messages (Msg) are the only thing that flows into a model's update().
|
|
2
|
+
//
|
|
3
|
+
// A Msg is just a tagged plain object — authors can define their own. These are
|
|
4
|
+
// the ones the runtime itself produces. We keep the shapes small and stable so
|
|
5
|
+
// they read the same as Bubble Tea's KeyMsg / WindowSizeMsg / QuitMsg.
|
|
6
|
+
|
|
7
|
+
// KeyMsg wraps a decoded key from bare-ansi-escapes' KeyDecoder. The raw fields
|
|
8
|
+
// (name, ctrl, meta, shift, sequence) are preserved; toString() renders the
|
|
9
|
+
// Bubble Tea-style chord ("ctrl+c", "up", "enter") so update() can match on a
|
|
10
|
+
// single string instead of juggling booleans.
|
|
11
|
+
class KeyMsg {
|
|
12
|
+
constructor(key) {
|
|
13
|
+
this.type = 'key'
|
|
14
|
+
this.name = key.name
|
|
15
|
+
this.sequence = key.sequence
|
|
16
|
+
this.ctrl = key.ctrl
|
|
17
|
+
this.meta = key.meta
|
|
18
|
+
this.shift = key.shift
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
toString() {
|
|
22
|
+
const parts = []
|
|
23
|
+
if (this.ctrl) parts.push('ctrl')
|
|
24
|
+
if (this.meta) parts.push('alt')
|
|
25
|
+
// Only surface shift for named keys; letters already arrive upper/lower.
|
|
26
|
+
if (this.shift && this.name && this.name.length > 1) parts.push('shift')
|
|
27
|
+
parts.push(this.name === 'return' ? 'enter' : this.name)
|
|
28
|
+
return parts.join('+')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// True if this key matches any of the given chords. A chord is matched
|
|
32
|
+
// against both the full string form ("ctrl+c", "enter") and the bare name
|
|
33
|
+
// ("c", "return"), so 'enter'/'return' and 'esc'/'escape' both work.
|
|
34
|
+
// if (msg.is('q', 'ctrl+c')) ...
|
|
35
|
+
is(...chords) {
|
|
36
|
+
const str = this.toString()
|
|
37
|
+
for (let chord of chords) {
|
|
38
|
+
if (chord === 'esc') chord = 'escape'
|
|
39
|
+
if (chord === str || chord === this.name) return true
|
|
40
|
+
}
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Emitted on startup and whenever the terminal is resized.
|
|
46
|
+
function windowSize(width, height) {
|
|
47
|
+
return { type: 'resize', width, height }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// The runtime tears down and exits when it sees this. `quit` (see commands.js)
|
|
51
|
+
// is the Cmd that produces it.
|
|
52
|
+
function quitMsg() {
|
|
53
|
+
return { type: 'quit' }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Wraps an error thrown by a Cmd so it can be handled in update() rather than
|
|
57
|
+
// crashing the loop.
|
|
58
|
+
function errorMsg(error) {
|
|
59
|
+
return { type: 'error', error }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = { KeyMsg, windowSize, quitMsg, errorMsg }
|
package/mouse.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Mouse support — SGR (1006) tracking and decoding.
|
|
2
|
+
//
|
|
3
|
+
// bare-ansi-escapes' KeyDecoder doesn't understand mouse reports, so the
|
|
4
|
+
// Program runs raw input through MouseParser first: it pulls SGR sequences
|
|
5
|
+
// (\x1b[<b;x;yM for press / motion, \x1b[<b;x;ym for release) out of the byte
|
|
6
|
+
// stream as MouseMsgs and forwards everything else to the key decoder.
|
|
7
|
+
//
|
|
8
|
+
// A MouseMsg looks like:
|
|
9
|
+
// { type: 'mouse', action, button, x, y, ctrl, alt, shift }
|
|
10
|
+
// action: 'press' | 'release' | 'motion' | 'wheel'
|
|
11
|
+
// button: 'left' | 'middle' | 'right' | 'none' | 'wheelup' | 'wheeldown'
|
|
12
|
+
// x, y: zero-indexed cell coordinates
|
|
13
|
+
const { constants } = require('bare-ansi-escapes')
|
|
14
|
+
const CSI = constants.CSI
|
|
15
|
+
|
|
16
|
+
const SGR = '?1006' // SGR extended coordinates (no 223-column cap, clean parse)
|
|
17
|
+
const MODES = {
|
|
18
|
+
basic: '?1000', // press / release
|
|
19
|
+
drag: '?1002', // + motion while a button is held
|
|
20
|
+
all: '?1003' // + motion with no button (hover)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function enable(mode = 'basic') {
|
|
24
|
+
const m = MODES[mode] || MODES.basic
|
|
25
|
+
return CSI + m + 'h' + CSI + SGR + 'h'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function disable(mode = 'basic') {
|
|
29
|
+
const m = MODES[mode] || MODES.basic
|
|
30
|
+
return CSI + SGR + 'l' + CSI + m + 'l'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const BUTTONS = ['left', 'middle', 'right', 'none']
|
|
34
|
+
|
|
35
|
+
// Decode the "b;x;y" body of an SGR mouse report plus its final char.
|
|
36
|
+
function decode(body, final) {
|
|
37
|
+
const parts = body.split(';')
|
|
38
|
+
if (parts.length !== 3) return null
|
|
39
|
+
const b = Number(parts[0])
|
|
40
|
+
const col = Number(parts[1])
|
|
41
|
+
const row = Number(parts[2])
|
|
42
|
+
if (!Number.isInteger(b) || !Number.isInteger(col) || !Number.isInteger(row)) {
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const mods = { ctrl: !!(b & 16), alt: !!(b & 8), shift: !!(b & 4) }
|
|
47
|
+
|
|
48
|
+
let action
|
|
49
|
+
let button
|
|
50
|
+
if (b & 64) {
|
|
51
|
+
action = 'wheel'
|
|
52
|
+
button = b & 1 ? 'wheeldown' : 'wheelup'
|
|
53
|
+
} else {
|
|
54
|
+
button = BUTTONS[b & 3]
|
|
55
|
+
action = b & 32 ? 'motion' : final === 'M' ? 'press' : 'release'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { type: 'mouse', action, button, x: col - 1, y: row - 1, ...mods }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Splits a byte stream into MouseMsgs and the remaining key bytes. Holds an
|
|
62
|
+
// incomplete trailing mouse sequence between feeds; uses latin1 throughout so
|
|
63
|
+
// non-mouse bytes (including 8-bit meta keys) round-trip to the decoder intact.
|
|
64
|
+
//
|
|
65
|
+
// Note: a mouse report split across two reads *before* its `<` arrives can't be
|
|
66
|
+
// distinguished from a key escape, so we only buffer once `\x1b[<` is seen.
|
|
67
|
+
// Terminals emit each report in a single write, so this is not a problem in
|
|
68
|
+
// practice.
|
|
69
|
+
class MouseParser {
|
|
70
|
+
constructor() {
|
|
71
|
+
this._partial = ''
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
feed(buf) {
|
|
75
|
+
const s = this._partial + buf.toString('latin1')
|
|
76
|
+
this._partial = ''
|
|
77
|
+
|
|
78
|
+
let keys = ''
|
|
79
|
+
const events = []
|
|
80
|
+
let i = 0
|
|
81
|
+
while (i < s.length) {
|
|
82
|
+
if (s[i] === '\x1b' && s[i + 1] === '[' && s[i + 2] === '<') {
|
|
83
|
+
let j = i + 3
|
|
84
|
+
while (j < s.length && s[j] !== 'M' && s[j] !== 'm') j++
|
|
85
|
+
if (j >= s.length) {
|
|
86
|
+
this._partial = s.slice(i) // incomplete report; wait for more
|
|
87
|
+
break
|
|
88
|
+
}
|
|
89
|
+
const ev = decode(s.slice(i + 3, j), s[j])
|
|
90
|
+
if (ev) events.push(ev)
|
|
91
|
+
i = j + 1
|
|
92
|
+
} else {
|
|
93
|
+
keys += s[i]
|
|
94
|
+
i++
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return { keys: Buffer.from(keys, 'latin1'), events }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = { enable, disable, decode, MouseParser, MODES }
|
package/package.json
CHANGED
|
@@ -1 +1,59 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"name": "bare-tui",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "A little TUI framework for Bare, based on bubbletea",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./package": "./package.json",
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./index.d.ts",
|
|
10
|
+
"default": "./index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"package.json",
|
|
15
|
+
"index.js",
|
|
16
|
+
"index.d.ts",
|
|
17
|
+
"ansi.js",
|
|
18
|
+
"commands.js",
|
|
19
|
+
"key.js",
|
|
20
|
+
"messages.js",
|
|
21
|
+
"mouse.js",
|
|
22
|
+
"program.js",
|
|
23
|
+
"renderer.js",
|
|
24
|
+
"style.js",
|
|
25
|
+
"components",
|
|
26
|
+
"docs",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"NOTICE"
|
|
30
|
+
],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"bare-ansi-escapes": "^2.2.3",
|
|
33
|
+
"bare-tty": "^5.1.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"bare-fs": "^4.7.1",
|
|
37
|
+
"bare-path": "^3.0.0",
|
|
38
|
+
"bare-stream": "^2.13.1",
|
|
39
|
+
"brittle": "^3.19.0",
|
|
40
|
+
"lunte": "^1.2.0",
|
|
41
|
+
"prettier": "^3.6.2",
|
|
42
|
+
"prettier-config-holepunch": "^2.0.0"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"format": "prettier --write . && lunte --fix",
|
|
46
|
+
"lint": "prettier --check . && lunte",
|
|
47
|
+
"test": "brittle-bare --coverage test/index.js"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "https://github.com/holepunchto/bare-tui.git"
|
|
52
|
+
},
|
|
53
|
+
"author": "Holepunch",
|
|
54
|
+
"license": "Apache-2.0",
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/holepunchto/bare-tui/issues"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/holepunchto/bare-tui"
|
|
59
|
+
}
|