@boba-cli/cursor 0.1.0-alpha.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/README.md +83 -0
- package/dist/index.cjs +184 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +87 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.js +179 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @boba-cli/cursor
|
|
2
|
+
|
|
3
|
+
Cursor component for Boba terminal UIs. Handles blinking, focus, and hidden/static modes.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @boba-cli/cursor
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import {
|
|
15
|
+
CursorModel,
|
|
16
|
+
CursorMode,
|
|
17
|
+
BlinkMsg,
|
|
18
|
+
InitialBlinkMsg,
|
|
19
|
+
} from '@boba-cli/cursor'
|
|
20
|
+
import type { Cmd, Msg } from '@boba-cli/tea'
|
|
21
|
+
|
|
22
|
+
// Create a cursor (defaults to blink mode)
|
|
23
|
+
let cursor = new CursorModel()
|
|
24
|
+
|
|
25
|
+
// In init, start blinking
|
|
26
|
+
function init(): Cmd<Msg> {
|
|
27
|
+
return cursor.initBlink()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// In update, handle focus/blur and blink messages
|
|
31
|
+
function update(msg: Msg): [MyModel, Cmd<Msg>] {
|
|
32
|
+
const [nextCursor, cmd] = cursor.update(msg)
|
|
33
|
+
cursor = nextCursor
|
|
34
|
+
return [model, cmd]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// In view, render the cursor at your desired position/character
|
|
38
|
+
function view(): string {
|
|
39
|
+
return cursor.view()
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Modes
|
|
44
|
+
|
|
45
|
+
| Mode | Behavior |
|
|
46
|
+
| ------------------- | ----------------------------------------------- |
|
|
47
|
+
| `CursorMode.Blink` | Toggles between block and text state on a timer |
|
|
48
|
+
| `CursorMode.Static` | Always shows the block |
|
|
49
|
+
| `CursorMode.Hidden` | Always shows text (cursor hidden) |
|
|
50
|
+
|
|
51
|
+
## API
|
|
52
|
+
|
|
53
|
+
| Export | Description |
|
|
54
|
+
| ----------------- | ----------------------------------- |
|
|
55
|
+
| `CursorModel` | Main component model |
|
|
56
|
+
| `CursorMode` | Enum of `Blink`, `Static`, `Hidden` |
|
|
57
|
+
| `BlinkMsg` | Blink toggle message |
|
|
58
|
+
| `InitialBlinkMsg` | Kickoff message for blinking |
|
|
59
|
+
|
|
60
|
+
### CursorModel helpers
|
|
61
|
+
|
|
62
|
+
| Method | Description |
|
|
63
|
+
| -------------------- | ---------------------------------------------- |
|
|
64
|
+
| `id()` | Unique ID for routing |
|
|
65
|
+
| `mode()` | Current mode |
|
|
66
|
+
| `initBlink()` | Command to send an initial blink message |
|
|
67
|
+
| `tickBlink()` | Command to schedule the next blink |
|
|
68
|
+
| `withMode(mode)` | Change mode (returns new model + optional cmd) |
|
|
69
|
+
| `withChar(char)` | Change the underlying character |
|
|
70
|
+
| `focus()` / `blur()` | Focus management (returns new model) |
|
|
71
|
+
| `update(msg)` | Handle messages; returns `[model, cmd]` |
|
|
72
|
+
| `view()` | Render the cursor |
|
|
73
|
+
|
|
74
|
+
## Scripts
|
|
75
|
+
|
|
76
|
+
- `pnpm -C packages/cursor build`
|
|
77
|
+
- `pnpm -C packages/cursor test`
|
|
78
|
+
- `pnpm -C packages/cursor lint`
|
|
79
|
+
- `pnpm -C packages/cursor generate:api-report`
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var tea = require('@boba-cli/tea');
|
|
4
|
+
var chapstick = require('@boba-cli/chapstick');
|
|
5
|
+
|
|
6
|
+
// src/model.ts
|
|
7
|
+
|
|
8
|
+
// src/messages.ts
|
|
9
|
+
var InitialBlinkMsg = class {
|
|
10
|
+
_tag = "cursor:init-blink";
|
|
11
|
+
};
|
|
12
|
+
var BlinkMsg = class {
|
|
13
|
+
constructor(id, tag, time) {
|
|
14
|
+
this.id = id;
|
|
15
|
+
this.tag = tag;
|
|
16
|
+
this.time = time;
|
|
17
|
+
}
|
|
18
|
+
_tag = "cursor:blink";
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/model.ts
|
|
22
|
+
var defaultBlinkSpeed = 530;
|
|
23
|
+
var lastId = 0;
|
|
24
|
+
function nextId() {
|
|
25
|
+
return ++lastId;
|
|
26
|
+
}
|
|
27
|
+
var CursorMode = /* @__PURE__ */ ((CursorMode2) => {
|
|
28
|
+
CursorMode2["Blink"] = "blink";
|
|
29
|
+
CursorMode2["Static"] = "static";
|
|
30
|
+
CursorMode2["Hidden"] = "hidden";
|
|
31
|
+
return CursorMode2;
|
|
32
|
+
})(CursorMode || {});
|
|
33
|
+
var CursorModel = class _CursorModel {
|
|
34
|
+
blinkSpeed;
|
|
35
|
+
style;
|
|
36
|
+
textStyle;
|
|
37
|
+
char;
|
|
38
|
+
#id;
|
|
39
|
+
#tag;
|
|
40
|
+
#blink;
|
|
41
|
+
#focus;
|
|
42
|
+
#mode;
|
|
43
|
+
constructor(options = {}, state) {
|
|
44
|
+
this.blinkSpeed = options.blinkSpeed ?? defaultBlinkSpeed;
|
|
45
|
+
this.style = options.style ?? new chapstick.Style();
|
|
46
|
+
this.textStyle = options.textStyle ?? new chapstick.Style();
|
|
47
|
+
this.char = options.char ?? " ";
|
|
48
|
+
this.#id = state?.id ?? nextId();
|
|
49
|
+
this.#tag = state?.tag ?? 0;
|
|
50
|
+
this.#blink = state?.blink ?? true;
|
|
51
|
+
this.#focus = state?.focus ?? false;
|
|
52
|
+
this.#mode = state?.mode ?? options.mode ?? "blink" /* Blink */;
|
|
53
|
+
}
|
|
54
|
+
/** Unique ID for this cursor (for message routing). */
|
|
55
|
+
id() {
|
|
56
|
+
return this.#id;
|
|
57
|
+
}
|
|
58
|
+
/** Current cursor mode. */
|
|
59
|
+
mode() {
|
|
60
|
+
return this.#mode;
|
|
61
|
+
}
|
|
62
|
+
/** Whether the cursor is currently in the "text" state (hidden block). */
|
|
63
|
+
isBlinkHidden() {
|
|
64
|
+
return this.#blink;
|
|
65
|
+
}
|
|
66
|
+
/** Whether this cursor currently has focus. */
|
|
67
|
+
isFocused() {
|
|
68
|
+
return this.#focus;
|
|
69
|
+
}
|
|
70
|
+
/** Kick off blinking at init (emits InitialBlinkMsg). */
|
|
71
|
+
initBlink() {
|
|
72
|
+
return () => new InitialBlinkMsg();
|
|
73
|
+
}
|
|
74
|
+
/** Command to schedule the next blink toggle. */
|
|
75
|
+
tickBlink() {
|
|
76
|
+
const nextTag = this.#tag + 1;
|
|
77
|
+
const id = this.#id;
|
|
78
|
+
const speed = this.blinkSpeed;
|
|
79
|
+
const next = this.#withState({ ...this.#state(), tag: nextTag });
|
|
80
|
+
const cmd = tea.tick(
|
|
81
|
+
speed,
|
|
82
|
+
(time) => new BlinkMsg(id, nextTag, time)
|
|
83
|
+
);
|
|
84
|
+
return [next, cmd];
|
|
85
|
+
}
|
|
86
|
+
/** Set the character under the cursor. */
|
|
87
|
+
withChar(char) {
|
|
88
|
+
return this.#withState(this.#state(), { char });
|
|
89
|
+
}
|
|
90
|
+
/** Set the cursor mode. Returns a new model and optional blink command. */
|
|
91
|
+
withMode(mode) {
|
|
92
|
+
const bounded = mode === "blink" /* Blink */ || mode === "static" /* Static */ || mode === "hidden" /* Hidden */ ? mode : "blink" /* Blink */;
|
|
93
|
+
const blink = bounded === "hidden" /* Hidden */ || !this.#focus ? true : this.#blink;
|
|
94
|
+
const next = this.#withState({ ...this.#state(), blink, mode: bounded });
|
|
95
|
+
if (bounded === "blink" /* Blink */ && this.#focus) {
|
|
96
|
+
const [scheduled, cmd] = next.tickBlink();
|
|
97
|
+
return [scheduled, cmd];
|
|
98
|
+
}
|
|
99
|
+
return [next, null];
|
|
100
|
+
}
|
|
101
|
+
/** Focus the cursor. Returns new model and optional blink command. */
|
|
102
|
+
focus() {
|
|
103
|
+
const blink = this.#mode === "hidden" /* Hidden */ ? true : this.#blink;
|
|
104
|
+
const next = this.#withState({ ...this.#state(), blink, focus: true });
|
|
105
|
+
if (this.#mode === "blink" /* Blink */) {
|
|
106
|
+
const [scheduled, cmd] = next.tickBlink();
|
|
107
|
+
return [scheduled, cmd];
|
|
108
|
+
}
|
|
109
|
+
return [next, null];
|
|
110
|
+
}
|
|
111
|
+
/** Blur the cursor. */
|
|
112
|
+
blur() {
|
|
113
|
+
return this.#withState({ ...this.#state(), blink: true, focus: false });
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Update the cursor model with an incoming message.
|
|
117
|
+
* Returns a new model and an optional command.
|
|
118
|
+
*/
|
|
119
|
+
update(msg) {
|
|
120
|
+
if (msg instanceof InitialBlinkMsg) {
|
|
121
|
+
if (this.#mode !== "blink" /* Blink */ || !this.#focus) {
|
|
122
|
+
return [this, null];
|
|
123
|
+
}
|
|
124
|
+
const [next, cmd] = this.tickBlink();
|
|
125
|
+
return [next, cmd];
|
|
126
|
+
}
|
|
127
|
+
if (msg instanceof tea.FocusMsg) {
|
|
128
|
+
return this.focus();
|
|
129
|
+
}
|
|
130
|
+
if (msg instanceof tea.BlurMsg) {
|
|
131
|
+
const next = this.blur();
|
|
132
|
+
return [next, null];
|
|
133
|
+
}
|
|
134
|
+
if (msg instanceof BlinkMsg) {
|
|
135
|
+
if (this.#mode !== "blink" /* Blink */ || !this.#focus) {
|
|
136
|
+
return [this, null];
|
|
137
|
+
}
|
|
138
|
+
if (msg.id !== this.#id || msg.tag !== this.#tag) {
|
|
139
|
+
return [this, null];
|
|
140
|
+
}
|
|
141
|
+
const toggled = this.#withState({ ...this.#state(), blink: !this.#blink });
|
|
142
|
+
const [next, cmd] = toggled.tickBlink();
|
|
143
|
+
return [next, cmd];
|
|
144
|
+
}
|
|
145
|
+
return [this, null];
|
|
146
|
+
}
|
|
147
|
+
/** Render the cursor. */
|
|
148
|
+
view() {
|
|
149
|
+
const char = this.char;
|
|
150
|
+
if (this.#blink) {
|
|
151
|
+
return this.textStyle.inline(true).render(char);
|
|
152
|
+
}
|
|
153
|
+
return this.style.inline(true).render(char);
|
|
154
|
+
}
|
|
155
|
+
#state() {
|
|
156
|
+
return {
|
|
157
|
+
id: this.#id,
|
|
158
|
+
tag: this.#tag,
|
|
159
|
+
blink: this.#blink,
|
|
160
|
+
focus: this.#focus,
|
|
161
|
+
mode: this.#mode
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
#withState(state, overrides) {
|
|
165
|
+
return new _CursorModel(
|
|
166
|
+
{
|
|
167
|
+
blinkSpeed: this.blinkSpeed,
|
|
168
|
+
style: this.style,
|
|
169
|
+
textStyle: this.textStyle,
|
|
170
|
+
char: overrides?.char ?? this.char,
|
|
171
|
+
mode: state.mode,
|
|
172
|
+
focused: state.focus
|
|
173
|
+
},
|
|
174
|
+
state
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
exports.BlinkMsg = BlinkMsg;
|
|
180
|
+
exports.CursorMode = CursorMode;
|
|
181
|
+
exports.CursorModel = CursorModel;
|
|
182
|
+
exports.InitialBlinkMsg = InitialBlinkMsg;
|
|
183
|
+
//# sourceMappingURL=index.cjs.map
|
|
184
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/messages.ts","../src/model.ts"],"names":["CursorMode","Style","tick","FocusMsg","BlurMsg"],"mappings":";;;;;;;;AAGO,IAAM,kBAAN,MAAsB;AAAA,EAClB,IAAA,GAAO,mBAAA;AAClB;AAKO,IAAM,WAAN,MAAe;AAAA,EAEpB,WAAA,CACkB,EAAA,EACA,GAAA,EACA,IAAA,EAChB;AAHgB,IAAA,IAAA,CAAA,EAAA,GAAA,EAAA;AACA,IAAA,IAAA,CAAA,GAAA,GAAA,GAAA;AACA,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AAAA,EACf;AAAA,EALM,IAAA,GAAO,cAAA;AAMlB;;;ACbA,IAAM,iBAAA,GAAoB,GAAA;AAG1B,IAAI,MAAA,GAAS,CAAA;AACb,SAAS,MAAA,GAAiB;AACxB,EAAA,OAAO,EAAE,MAAA;AACX;AAGO,IAAK,UAAA,qBAAAA,WAAAA,KAAL;AACL,EAAAA,YAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,YAAA,QAAA,CAAA,GAAS,QAAA;AACT,EAAAA,YAAA,QAAA,CAAA,GAAS,QAAA;AAHC,EAAA,OAAAA,WAAAA;AAAA,CAAA,EAAA,UAAA,IAAA,EAAA;AAgCL,IAAM,WAAA,GAAN,MAAM,YAAA,CAAY;AAAA,EACd,UAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EAET,WAAA,CAAY,OAAA,GAAyB,EAAC,EAAG,KAAA,EAAqB;AAC5D,IAAA,IAAA,CAAK,UAAA,GAAa,QAAQ,UAAA,IAAc,iBAAA;AACxC,IAAA,IAAA,CAAK,KAAA,GAAQ,OAAA,CAAQ,KAAA,IAAS,IAAIC,eAAA,EAAM;AACxC,IAAA,IAAA,CAAK,SAAA,GAAY,OAAA,CAAQ,SAAA,IAAa,IAAIA,eAAA,EAAM;AAChD,IAAA,IAAA,CAAK,IAAA,GAAO,QAAQ,IAAA,IAAQ,GAAA;AAC5B,IAAA,IAAA,CAAK,GAAA,GAAM,KAAA,EAAO,EAAA,IAAM,MAAA,EAAO;AAC/B,IAAA,IAAA,CAAK,IAAA,GAAO,OAAO,GAAA,IAAO,CAAA;AAC1B,IAAA,IAAA,CAAK,MAAA,GAAS,OAAO,KAAA,IAAS,IAAA;AAC9B,IAAA,IAAA,CAAK,MAAA,GAAS,OAAO,KAAA,IAAS,KAAA;AAC9B,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA,EAAO,IAAA,IAAQ,OAAA,CAAQ,IAAA,IAAQ,OAAA;AAAA,EAC9C;AAAA;AAAA,EAGA,EAAA,GAAa;AACX,IAAA,OAAO,IAAA,CAAK,GAAA;AAAA,EACd;AAAA;AAAA,EAGA,IAAA,GAAmB;AACjB,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA;AAAA,EAGA,aAAA,GAAyB;AACvB,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AAAA;AAAA,EAGA,SAAA,GAAqB;AACnB,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AAAA;AAAA,EAGA,SAAA,GAAkC;AAChC,IAAA,OAAO,MAAM,IAAI,eAAA,EAAgB;AAAA,EACnC;AAAA;AAAA,EAGA,SAAA,GAA0C;AACxC,IAAA,MAAM,OAAA,GAAU,KAAK,IAAA,GAAO,CAAA;AAC5B,IAAA,MAAM,KAAK,IAAA,CAAK,GAAA;AAChB,IAAA,MAAM,QAAQ,IAAA,CAAK,UAAA;AACnB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,UAAA,CAAW,EAAE,GAAG,KAAK,MAAA,EAAO,EAAG,GAAA,EAAK,OAAA,EAAS,CAAA;AAC/D,IAAA,MAAM,GAAA,GAAqBC,QAAA;AAAA,MACzB,KAAA;AAAA,MACA,CAAC,IAAA,KAAe,IAAI,QAAA,CAAS,EAAA,EAAI,SAAS,IAAI;AAAA,KAChD;AACA,IAAA,OAAO,CAAC,MAAM,GAAG,CAAA;AAAA,EACnB;AAAA;AAAA,EAGA,SAAS,IAAA,EAA2B;AAClC,IAAA,OAAO,KAAK,UAAA,CAAW,IAAA,CAAK,QAAO,EAAG,EAAE,MAAM,CAAA;AAAA,EAChD;AAAA;AAAA,EAGA,SAAS,IAAA,EAA2C;AAClD,IAAA,MAAM,UACJ,IAAA,KAAS,OAAA,gBACT,SAAS,QAAA,iBACT,IAAA,KAAS,wBACL,IAAA,GACA,OAAA;AAEN,IAAA,MAAM,QACJ,OAAA,KAAY,QAAA,iBAAqB,CAAC,IAAA,CAAK,MAAA,GAAS,OAAO,IAAA,CAAK,MAAA;AAC9D,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,UAAA,CAAW,EAAE,GAAG,IAAA,CAAK,MAAA,EAAO,EAAG,KAAA,EAAO,IAAA,EAAM,OAAA,EAAS,CAAA;AAEvE,IAAA,IAAI,OAAA,KAAY,OAAA,gBAAoB,IAAA,CAAK,MAAA,EAAQ;AAC/C,MAAA,MAAM,CAAC,SAAA,EAAW,GAAG,CAAA,GAAI,KAAK,SAAA,EAAU;AACxC,MAAA,OAAO,CAAC,WAAW,GAAG,CAAA;AAAA,IACxB;AACA,IAAA,OAAO,CAAC,MAAM,IAAI,CAAA;AAAA,EACpB;AAAA;AAAA,EAGA,KAAA,GAAiC;AAC/B,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,KAAU,QAAA,gBAAoB,OAAO,IAAA,CAAK,MAAA;AAC7D,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,UAAA,CAAW,EAAE,GAAG,IAAA,CAAK,MAAA,EAAO,EAAG,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,CAAA;AACrE,IAAA,IAAI,IAAA,CAAK,UAAU,OAAA,cAAkB;AACnC,MAAA,MAAM,CAAC,SAAA,EAAW,GAAG,CAAA,GAAI,KAAK,SAAA,EAAU;AACxC,MAAA,OAAO,CAAC,WAAW,GAAG,CAAA;AAAA,IACxB;AACA,IAAA,OAAO,CAAC,MAAM,IAAI,CAAA;AAAA,EACpB;AAAA;AAAA,EAGA,IAAA,GAAoB;AAClB,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,EAAE,GAAG,IAAA,CAAK,MAAA,EAAO,EAAG,KAAA,EAAO,IAAA,EAAM,KAAA,EAAO,KAAA,EAAO,CAAA;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,GAAA,EAAmC;AACxC,IAAA,IAAI,eAAe,eAAA,EAAiB;AAClC,MAAA,IAAI,IAAA,CAAK,KAAA,KAAU,OAAA,gBAAoB,CAAC,KAAK,MAAA,EAAQ;AACnD,QAAA,OAAO,CAAC,MAAM,IAAI,CAAA;AAAA,MACpB;AACA,MAAA,MAAM,CAAC,IAAA,EAAM,GAAG,CAAA,GAAI,KAAK,SAAA,EAAU;AACnC,MAAA,OAAO,CAAC,MAAM,GAAe,CAAA;AAAA,IAC/B;AAEA,IAAA,IAAI,eAAeC,YAAA,EAAU;AAC3B,MAAA,OAAO,KAAK,KAAA,EAAM;AAAA,IACpB;AAEA,IAAA,IAAI,eAAeC,WAAA,EAAS;AAC1B,MAAA,MAAM,IAAA,GAAO,KAAK,IAAA,EAAK;AACvB,MAAA,OAAO,CAAC,MAAM,IAAI,CAAA;AAAA,IACpB;AAEA,IAAA,IAAI,eAAe,QAAA,EAAU;AAE3B,MAAA,IAAI,IAAA,CAAK,KAAA,KAAU,OAAA,gBAAoB,CAAC,KAAK,MAAA,EAAQ;AACnD,QAAA,OAAO,CAAC,MAAM,IAAI,CAAA;AAAA,MACpB;AACA,MAAA,IAAI,IAAI,EAAA,KAAO,IAAA,CAAK,OAAO,GAAA,CAAI,GAAA,KAAQ,KAAK,IAAA,EAAM;AAChD,QAAA,OAAO,CAAC,MAAM,IAAI,CAAA;AAAA,MACpB;AAEA,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,UAAA,CAAW,EAAE,GAAG,IAAA,CAAK,MAAA,EAAO,EAAG,KAAA,EAAO,CAAC,IAAA,CAAK,MAAA,EAAQ,CAAA;AACzE,MAAA,MAAM,CAAC,IAAA,EAAM,GAAG,CAAA,GAAI,QAAQ,SAAA,EAAU;AACtC,MAAA,OAAO,CAAC,MAAM,GAAe,CAAA;AAAA,IAC/B;AAEA,IAAA,OAAO,CAAC,MAAM,IAAI,CAAA;AAAA,EACpB;AAAA;AAAA,EAGA,IAAA,GAAe;AACb,IAAA,MAAM,OAAO,IAAA,CAAK,IAAA;AAClB,IAAA,IAAI,KAAK,MAAA,EAAQ;AACf,MAAA,OAAO,KAAK,SAAA,CAAU,MAAA,CAAO,IAAI,CAAA,CAAE,OAAO,IAAI,CAAA;AAAA,IAChD;AACA,IAAA,OAAO,KAAK,KAAA,CAAM,MAAA,CAAO,IAAI,CAAA,CAAE,OAAO,IAAI,CAAA;AAAA,EAC5C;AAAA,EAEA,MAAA,GAAsB;AACpB,IAAA,OAAO;AAAA,MACL,IAAI,IAAA,CAAK,GAAA;AAAA,MACT,KAAK,IAAA,CAAK,IAAA;AAAA,MACV,OAAO,IAAA,CAAK,MAAA;AAAA,MACZ,OAAO,IAAA,CAAK,MAAA;AAAA,MACZ,MAAM,IAAA,CAAK;AAAA,KACb;AAAA,EACF;AAAA,EAEA,UAAA,CAAW,OAAoB,SAAA,EAA4C;AACzE,IAAA,OAAO,IAAI,YAAA;AAAA,MACT;AAAA,QACE,YAAY,IAAA,CAAK,UAAA;AAAA,QACjB,OAAO,IAAA,CAAK,KAAA;AAAA,QACZ,WAAW,IAAA,CAAK,SAAA;AAAA,QAChB,IAAA,EAAM,SAAA,EAAW,IAAA,IAAQ,IAAA,CAAK,IAAA;AAAA,QAC9B,MAAM,KAAA,CAAM,IAAA;AAAA,QACZ,SAAS,KAAA,CAAM;AAAA,OACjB;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF","file":"index.cjs","sourcesContent":["/**\n * @public\n */\nexport class InitialBlinkMsg {\n readonly _tag = 'cursor:init-blink'\n}\n\n/**\n * @public\n */\nexport class BlinkMsg {\n readonly _tag = 'cursor:blink'\n constructor(\n public readonly id: number,\n public readonly tag: number,\n public readonly time: Date,\n ) {}\n}\n","import { tick, type Cmd, type Msg, FocusMsg, BlurMsg } from '@boba-cli/tea'\nimport { Style } from '@boba-cli/chapstick'\nimport { BlinkMsg, InitialBlinkMsg } from './messages.js'\n\nconst defaultBlinkSpeed = 530 // ms\n\n// Module-level ID counter for unique cursor identification\nlet lastId = 0\nfunction nextId(): number {\n return ++lastId\n}\n\n/** Available cursor behaviors. @public */\nexport enum CursorMode {\n Blink = 'blink',\n Static = 'static',\n Hidden = 'hidden',\n}\n\n/** Options for creating a CursorModel. @public */\nexport interface CursorOptions {\n blinkSpeed?: number\n style?: Style\n textStyle?: Style\n char?: string\n mode?: CursorMode\n focused?: boolean\n}\n\ntype CursorState = {\n id: number\n tag: number\n blink: boolean\n focus: boolean\n mode: CursorMode\n}\n\n/**\n * Cursor component model.\n *\n * Use `tickBlink()` to start blinking, or handle `InitialBlinkMsg` from `initBlink()`.\n * Handle `BlinkMsg`, `FocusMsg`, and `BlurMsg` in your update loop.\n *\n * @public\n */\nexport class CursorModel {\n readonly blinkSpeed: number\n readonly style: Style\n readonly textStyle: Style\n readonly char: string\n readonly #id: number\n readonly #tag: number\n readonly #blink: boolean\n readonly #focus: boolean\n readonly #mode: CursorMode\n\n constructor(options: CursorOptions = {}, state?: CursorState) {\n this.blinkSpeed = options.blinkSpeed ?? defaultBlinkSpeed\n this.style = options.style ?? new Style()\n this.textStyle = options.textStyle ?? new Style()\n this.char = options.char ?? ' '\n this.#id = state?.id ?? nextId()\n this.#tag = state?.tag ?? 0\n this.#blink = state?.blink ?? true\n this.#focus = state?.focus ?? false\n this.#mode = state?.mode ?? options.mode ?? CursorMode.Blink\n }\n\n /** Unique ID for this cursor (for message routing). */\n id(): number {\n return this.#id\n }\n\n /** Current cursor mode. */\n mode(): CursorMode {\n return this.#mode\n }\n\n /** Whether the cursor is currently in the \"text\" state (hidden block). */\n isBlinkHidden(): boolean {\n return this.#blink\n }\n\n /** Whether this cursor currently has focus. */\n isFocused(): boolean {\n return this.#focus\n }\n\n /** Kick off blinking at init (emits InitialBlinkMsg). */\n initBlink(): Cmd<InitialBlinkMsg> {\n return () => new InitialBlinkMsg()\n }\n\n /** Command to schedule the next blink toggle. */\n tickBlink(): [CursorModel, Cmd<BlinkMsg>] {\n const nextTag = this.#tag + 1\n const id = this.#id\n const speed = this.blinkSpeed\n const next = this.#withState({ ...this.#state(), tag: nextTag })\n const cmd: Cmd<BlinkMsg> = tick(\n speed,\n (time: Date) => new BlinkMsg(id, nextTag, time),\n )\n return [next, cmd]\n }\n\n /** Set the character under the cursor. */\n withChar(char: string): CursorModel {\n return this.#withState(this.#state(), { char })\n }\n\n /** Set the cursor mode. Returns a new model and optional blink command. */\n withMode(mode: CursorMode): [CursorModel, Cmd<Msg>] {\n const bounded =\n mode === CursorMode.Blink ||\n mode === CursorMode.Static ||\n mode === CursorMode.Hidden\n ? mode\n : CursorMode.Blink\n\n const blink =\n bounded === CursorMode.Hidden || !this.#focus ? true : this.#blink\n const next = this.#withState({ ...this.#state(), blink, mode: bounded })\n\n if (bounded === CursorMode.Blink && this.#focus) {\n const [scheduled, cmd] = next.tickBlink()\n return [scheduled, cmd]\n }\n return [next, null]\n }\n\n /** Focus the cursor. Returns new model and optional blink command. */\n focus(): [CursorModel, Cmd<Msg>] {\n const blink = this.#mode === CursorMode.Hidden ? true : this.#blink\n const next = this.#withState({ ...this.#state(), blink, focus: true })\n if (this.#mode === CursorMode.Blink) {\n const [scheduled, cmd] = next.tickBlink()\n return [scheduled, cmd]\n }\n return [next, null]\n }\n\n /** Blur the cursor. */\n blur(): CursorModel {\n return this.#withState({ ...this.#state(), blink: true, focus: false })\n }\n\n /**\n * Update the cursor model with an incoming message.\n * Returns a new model and an optional command.\n */\n update(msg: Msg): [CursorModel, Cmd<Msg>] {\n if (msg instanceof InitialBlinkMsg) {\n if (this.#mode !== CursorMode.Blink || !this.#focus) {\n return [this, null]\n }\n const [next, cmd] = this.tickBlink()\n return [next, cmd as Cmd<Msg>]\n }\n\n if (msg instanceof FocusMsg) {\n return this.focus()\n }\n\n if (msg instanceof BlurMsg) {\n const next = this.blur()\n return [next, null]\n }\n\n if (msg instanceof BlinkMsg) {\n // Is this model blink-able and expecting this tick?\n if (this.#mode !== CursorMode.Blink || !this.#focus) {\n return [this, null]\n }\n if (msg.id !== this.#id || msg.tag !== this.#tag) {\n return [this, null]\n }\n\n const toggled = this.#withState({ ...this.#state(), blink: !this.#blink })\n const [next, cmd] = toggled.tickBlink()\n return [next, cmd as Cmd<Msg>]\n }\n\n return [this, null]\n }\n\n /** Render the cursor. */\n view(): string {\n const char = this.char\n if (this.#blink) {\n return this.textStyle.inline(true).render(char)\n }\n return this.style.inline(true).render(char)\n }\n\n #state(): CursorState {\n return {\n id: this.#id,\n tag: this.#tag,\n blink: this.#blink,\n focus: this.#focus,\n mode: this.#mode,\n }\n }\n\n #withState(state: CursorState, overrides?: { char?: string }): CursorModel {\n return new CursorModel(\n {\n blinkSpeed: this.blinkSpeed,\n style: this.style,\n textStyle: this.textStyle,\n char: overrides?.char ?? this.char,\n mode: state.mode,\n focused: state.focus,\n },\n state,\n )\n }\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Cmd, Msg } from '@boba-cli/tea';
|
|
2
|
+
import { Style } from '@boba-cli/chapstick';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
declare class InitialBlinkMsg {
|
|
8
|
+
readonly _tag = "cursor:init-blink";
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
declare class BlinkMsg {
|
|
14
|
+
readonly id: number;
|
|
15
|
+
readonly tag: number;
|
|
16
|
+
readonly time: Date;
|
|
17
|
+
readonly _tag = "cursor:blink";
|
|
18
|
+
constructor(id: number, tag: number, time: Date);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Available cursor behaviors. @public */
|
|
22
|
+
declare enum CursorMode {
|
|
23
|
+
Blink = "blink",
|
|
24
|
+
Static = "static",
|
|
25
|
+
Hidden = "hidden"
|
|
26
|
+
}
|
|
27
|
+
/** Options for creating a CursorModel. @public */
|
|
28
|
+
interface CursorOptions {
|
|
29
|
+
blinkSpeed?: number;
|
|
30
|
+
style?: Style;
|
|
31
|
+
textStyle?: Style;
|
|
32
|
+
char?: string;
|
|
33
|
+
mode?: CursorMode;
|
|
34
|
+
focused?: boolean;
|
|
35
|
+
}
|
|
36
|
+
type CursorState = {
|
|
37
|
+
id: number;
|
|
38
|
+
tag: number;
|
|
39
|
+
blink: boolean;
|
|
40
|
+
focus: boolean;
|
|
41
|
+
mode: CursorMode;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Cursor component model.
|
|
45
|
+
*
|
|
46
|
+
* Use `tickBlink()` to start blinking, or handle `InitialBlinkMsg` from `initBlink()`.
|
|
47
|
+
* Handle `BlinkMsg`, `FocusMsg`, and `BlurMsg` in your update loop.
|
|
48
|
+
*
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
declare class CursorModel {
|
|
52
|
+
#private;
|
|
53
|
+
readonly blinkSpeed: number;
|
|
54
|
+
readonly style: Style;
|
|
55
|
+
readonly textStyle: Style;
|
|
56
|
+
readonly char: string;
|
|
57
|
+
constructor(options?: CursorOptions, state?: CursorState);
|
|
58
|
+
/** Unique ID for this cursor (for message routing). */
|
|
59
|
+
id(): number;
|
|
60
|
+
/** Current cursor mode. */
|
|
61
|
+
mode(): CursorMode;
|
|
62
|
+
/** Whether the cursor is currently in the "text" state (hidden block). */
|
|
63
|
+
isBlinkHidden(): boolean;
|
|
64
|
+
/** Whether this cursor currently has focus. */
|
|
65
|
+
isFocused(): boolean;
|
|
66
|
+
/** Kick off blinking at init (emits InitialBlinkMsg). */
|
|
67
|
+
initBlink(): Cmd<InitialBlinkMsg>;
|
|
68
|
+
/** Command to schedule the next blink toggle. */
|
|
69
|
+
tickBlink(): [CursorModel, Cmd<BlinkMsg>];
|
|
70
|
+
/** Set the character under the cursor. */
|
|
71
|
+
withChar(char: string): CursorModel;
|
|
72
|
+
/** Set the cursor mode. Returns a new model and optional blink command. */
|
|
73
|
+
withMode(mode: CursorMode): [CursorModel, Cmd<Msg>];
|
|
74
|
+
/** Focus the cursor. Returns new model and optional blink command. */
|
|
75
|
+
focus(): [CursorModel, Cmd<Msg>];
|
|
76
|
+
/** Blur the cursor. */
|
|
77
|
+
blur(): CursorModel;
|
|
78
|
+
/**
|
|
79
|
+
* Update the cursor model with an incoming message.
|
|
80
|
+
* Returns a new model and an optional command.
|
|
81
|
+
*/
|
|
82
|
+
update(msg: Msg): [CursorModel, Cmd<Msg>];
|
|
83
|
+
/** Render the cursor. */
|
|
84
|
+
view(): string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { BlinkMsg, CursorMode, CursorModel, type CursorOptions, InitialBlinkMsg };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Cmd, Msg } from '@boba-cli/tea';
|
|
2
|
+
import { Style } from '@boba-cli/chapstick';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
declare class InitialBlinkMsg {
|
|
8
|
+
readonly _tag = "cursor:init-blink";
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
declare class BlinkMsg {
|
|
14
|
+
readonly id: number;
|
|
15
|
+
readonly tag: number;
|
|
16
|
+
readonly time: Date;
|
|
17
|
+
readonly _tag = "cursor:blink";
|
|
18
|
+
constructor(id: number, tag: number, time: Date);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Available cursor behaviors. @public */
|
|
22
|
+
declare enum CursorMode {
|
|
23
|
+
Blink = "blink",
|
|
24
|
+
Static = "static",
|
|
25
|
+
Hidden = "hidden"
|
|
26
|
+
}
|
|
27
|
+
/** Options for creating a CursorModel. @public */
|
|
28
|
+
interface CursorOptions {
|
|
29
|
+
blinkSpeed?: number;
|
|
30
|
+
style?: Style;
|
|
31
|
+
textStyle?: Style;
|
|
32
|
+
char?: string;
|
|
33
|
+
mode?: CursorMode;
|
|
34
|
+
focused?: boolean;
|
|
35
|
+
}
|
|
36
|
+
type CursorState = {
|
|
37
|
+
id: number;
|
|
38
|
+
tag: number;
|
|
39
|
+
blink: boolean;
|
|
40
|
+
focus: boolean;
|
|
41
|
+
mode: CursorMode;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Cursor component model.
|
|
45
|
+
*
|
|
46
|
+
* Use `tickBlink()` to start blinking, or handle `InitialBlinkMsg` from `initBlink()`.
|
|
47
|
+
* Handle `BlinkMsg`, `FocusMsg`, and `BlurMsg` in your update loop.
|
|
48
|
+
*
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
declare class CursorModel {
|
|
52
|
+
#private;
|
|
53
|
+
readonly blinkSpeed: number;
|
|
54
|
+
readonly style: Style;
|
|
55
|
+
readonly textStyle: Style;
|
|
56
|
+
readonly char: string;
|
|
57
|
+
constructor(options?: CursorOptions, state?: CursorState);
|
|
58
|
+
/** Unique ID for this cursor (for message routing). */
|
|
59
|
+
id(): number;
|
|
60
|
+
/** Current cursor mode. */
|
|
61
|
+
mode(): CursorMode;
|
|
62
|
+
/** Whether the cursor is currently in the "text" state (hidden block). */
|
|
63
|
+
isBlinkHidden(): boolean;
|
|
64
|
+
/** Whether this cursor currently has focus. */
|
|
65
|
+
isFocused(): boolean;
|
|
66
|
+
/** Kick off blinking at init (emits InitialBlinkMsg). */
|
|
67
|
+
initBlink(): Cmd<InitialBlinkMsg>;
|
|
68
|
+
/** Command to schedule the next blink toggle. */
|
|
69
|
+
tickBlink(): [CursorModel, Cmd<BlinkMsg>];
|
|
70
|
+
/** Set the character under the cursor. */
|
|
71
|
+
withChar(char: string): CursorModel;
|
|
72
|
+
/** Set the cursor mode. Returns a new model and optional blink command. */
|
|
73
|
+
withMode(mode: CursorMode): [CursorModel, Cmd<Msg>];
|
|
74
|
+
/** Focus the cursor. Returns new model and optional blink command. */
|
|
75
|
+
focus(): [CursorModel, Cmd<Msg>];
|
|
76
|
+
/** Blur the cursor. */
|
|
77
|
+
blur(): CursorModel;
|
|
78
|
+
/**
|
|
79
|
+
* Update the cursor model with an incoming message.
|
|
80
|
+
* Returns a new model and an optional command.
|
|
81
|
+
*/
|
|
82
|
+
update(msg: Msg): [CursorModel, Cmd<Msg>];
|
|
83
|
+
/** Render the cursor. */
|
|
84
|
+
view(): string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { BlinkMsg, CursorMode, CursorModel, type CursorOptions, InitialBlinkMsg };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { tick, FocusMsg, BlurMsg } from '@boba-cli/tea';
|
|
2
|
+
import { Style } from '@boba-cli/chapstick';
|
|
3
|
+
|
|
4
|
+
// src/model.ts
|
|
5
|
+
|
|
6
|
+
// src/messages.ts
|
|
7
|
+
var InitialBlinkMsg = class {
|
|
8
|
+
_tag = "cursor:init-blink";
|
|
9
|
+
};
|
|
10
|
+
var BlinkMsg = class {
|
|
11
|
+
constructor(id, tag, time) {
|
|
12
|
+
this.id = id;
|
|
13
|
+
this.tag = tag;
|
|
14
|
+
this.time = time;
|
|
15
|
+
}
|
|
16
|
+
_tag = "cursor:blink";
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/model.ts
|
|
20
|
+
var defaultBlinkSpeed = 530;
|
|
21
|
+
var lastId = 0;
|
|
22
|
+
function nextId() {
|
|
23
|
+
return ++lastId;
|
|
24
|
+
}
|
|
25
|
+
var CursorMode = /* @__PURE__ */ ((CursorMode2) => {
|
|
26
|
+
CursorMode2["Blink"] = "blink";
|
|
27
|
+
CursorMode2["Static"] = "static";
|
|
28
|
+
CursorMode2["Hidden"] = "hidden";
|
|
29
|
+
return CursorMode2;
|
|
30
|
+
})(CursorMode || {});
|
|
31
|
+
var CursorModel = class _CursorModel {
|
|
32
|
+
blinkSpeed;
|
|
33
|
+
style;
|
|
34
|
+
textStyle;
|
|
35
|
+
char;
|
|
36
|
+
#id;
|
|
37
|
+
#tag;
|
|
38
|
+
#blink;
|
|
39
|
+
#focus;
|
|
40
|
+
#mode;
|
|
41
|
+
constructor(options = {}, state) {
|
|
42
|
+
this.blinkSpeed = options.blinkSpeed ?? defaultBlinkSpeed;
|
|
43
|
+
this.style = options.style ?? new Style();
|
|
44
|
+
this.textStyle = options.textStyle ?? new Style();
|
|
45
|
+
this.char = options.char ?? " ";
|
|
46
|
+
this.#id = state?.id ?? nextId();
|
|
47
|
+
this.#tag = state?.tag ?? 0;
|
|
48
|
+
this.#blink = state?.blink ?? true;
|
|
49
|
+
this.#focus = state?.focus ?? false;
|
|
50
|
+
this.#mode = state?.mode ?? options.mode ?? "blink" /* Blink */;
|
|
51
|
+
}
|
|
52
|
+
/** Unique ID for this cursor (for message routing). */
|
|
53
|
+
id() {
|
|
54
|
+
return this.#id;
|
|
55
|
+
}
|
|
56
|
+
/** Current cursor mode. */
|
|
57
|
+
mode() {
|
|
58
|
+
return this.#mode;
|
|
59
|
+
}
|
|
60
|
+
/** Whether the cursor is currently in the "text" state (hidden block). */
|
|
61
|
+
isBlinkHidden() {
|
|
62
|
+
return this.#blink;
|
|
63
|
+
}
|
|
64
|
+
/** Whether this cursor currently has focus. */
|
|
65
|
+
isFocused() {
|
|
66
|
+
return this.#focus;
|
|
67
|
+
}
|
|
68
|
+
/** Kick off blinking at init (emits InitialBlinkMsg). */
|
|
69
|
+
initBlink() {
|
|
70
|
+
return () => new InitialBlinkMsg();
|
|
71
|
+
}
|
|
72
|
+
/** Command to schedule the next blink toggle. */
|
|
73
|
+
tickBlink() {
|
|
74
|
+
const nextTag = this.#tag + 1;
|
|
75
|
+
const id = this.#id;
|
|
76
|
+
const speed = this.blinkSpeed;
|
|
77
|
+
const next = this.#withState({ ...this.#state(), tag: nextTag });
|
|
78
|
+
const cmd = tick(
|
|
79
|
+
speed,
|
|
80
|
+
(time) => new BlinkMsg(id, nextTag, time)
|
|
81
|
+
);
|
|
82
|
+
return [next, cmd];
|
|
83
|
+
}
|
|
84
|
+
/** Set the character under the cursor. */
|
|
85
|
+
withChar(char) {
|
|
86
|
+
return this.#withState(this.#state(), { char });
|
|
87
|
+
}
|
|
88
|
+
/** Set the cursor mode. Returns a new model and optional blink command. */
|
|
89
|
+
withMode(mode) {
|
|
90
|
+
const bounded = mode === "blink" /* Blink */ || mode === "static" /* Static */ || mode === "hidden" /* Hidden */ ? mode : "blink" /* Blink */;
|
|
91
|
+
const blink = bounded === "hidden" /* Hidden */ || !this.#focus ? true : this.#blink;
|
|
92
|
+
const next = this.#withState({ ...this.#state(), blink, mode: bounded });
|
|
93
|
+
if (bounded === "blink" /* Blink */ && this.#focus) {
|
|
94
|
+
const [scheduled, cmd] = next.tickBlink();
|
|
95
|
+
return [scheduled, cmd];
|
|
96
|
+
}
|
|
97
|
+
return [next, null];
|
|
98
|
+
}
|
|
99
|
+
/** Focus the cursor. Returns new model and optional blink command. */
|
|
100
|
+
focus() {
|
|
101
|
+
const blink = this.#mode === "hidden" /* Hidden */ ? true : this.#blink;
|
|
102
|
+
const next = this.#withState({ ...this.#state(), blink, focus: true });
|
|
103
|
+
if (this.#mode === "blink" /* Blink */) {
|
|
104
|
+
const [scheduled, cmd] = next.tickBlink();
|
|
105
|
+
return [scheduled, cmd];
|
|
106
|
+
}
|
|
107
|
+
return [next, null];
|
|
108
|
+
}
|
|
109
|
+
/** Blur the cursor. */
|
|
110
|
+
blur() {
|
|
111
|
+
return this.#withState({ ...this.#state(), blink: true, focus: false });
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Update the cursor model with an incoming message.
|
|
115
|
+
* Returns a new model and an optional command.
|
|
116
|
+
*/
|
|
117
|
+
update(msg) {
|
|
118
|
+
if (msg instanceof InitialBlinkMsg) {
|
|
119
|
+
if (this.#mode !== "blink" /* Blink */ || !this.#focus) {
|
|
120
|
+
return [this, null];
|
|
121
|
+
}
|
|
122
|
+
const [next, cmd] = this.tickBlink();
|
|
123
|
+
return [next, cmd];
|
|
124
|
+
}
|
|
125
|
+
if (msg instanceof FocusMsg) {
|
|
126
|
+
return this.focus();
|
|
127
|
+
}
|
|
128
|
+
if (msg instanceof BlurMsg) {
|
|
129
|
+
const next = this.blur();
|
|
130
|
+
return [next, null];
|
|
131
|
+
}
|
|
132
|
+
if (msg instanceof BlinkMsg) {
|
|
133
|
+
if (this.#mode !== "blink" /* Blink */ || !this.#focus) {
|
|
134
|
+
return [this, null];
|
|
135
|
+
}
|
|
136
|
+
if (msg.id !== this.#id || msg.tag !== this.#tag) {
|
|
137
|
+
return [this, null];
|
|
138
|
+
}
|
|
139
|
+
const toggled = this.#withState({ ...this.#state(), blink: !this.#blink });
|
|
140
|
+
const [next, cmd] = toggled.tickBlink();
|
|
141
|
+
return [next, cmd];
|
|
142
|
+
}
|
|
143
|
+
return [this, null];
|
|
144
|
+
}
|
|
145
|
+
/** Render the cursor. */
|
|
146
|
+
view() {
|
|
147
|
+
const char = this.char;
|
|
148
|
+
if (this.#blink) {
|
|
149
|
+
return this.textStyle.inline(true).render(char);
|
|
150
|
+
}
|
|
151
|
+
return this.style.inline(true).render(char);
|
|
152
|
+
}
|
|
153
|
+
#state() {
|
|
154
|
+
return {
|
|
155
|
+
id: this.#id,
|
|
156
|
+
tag: this.#tag,
|
|
157
|
+
blink: this.#blink,
|
|
158
|
+
focus: this.#focus,
|
|
159
|
+
mode: this.#mode
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
#withState(state, overrides) {
|
|
163
|
+
return new _CursorModel(
|
|
164
|
+
{
|
|
165
|
+
blinkSpeed: this.blinkSpeed,
|
|
166
|
+
style: this.style,
|
|
167
|
+
textStyle: this.textStyle,
|
|
168
|
+
char: overrides?.char ?? this.char,
|
|
169
|
+
mode: state.mode,
|
|
170
|
+
focused: state.focus
|
|
171
|
+
},
|
|
172
|
+
state
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export { BlinkMsg, CursorMode, CursorModel, InitialBlinkMsg };
|
|
178
|
+
//# sourceMappingURL=index.js.map
|
|
179
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/messages.ts","../src/model.ts"],"names":["CursorMode"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @public\n */\nexport class InitialBlinkMsg {\n readonly _tag = 'cursor:init-blink'\n}\n\n/**\n * @public\n */\nexport class BlinkMsg {\n readonly _tag = 'cursor:blink'\n constructor(\n public readonly id: number,\n public readonly tag: number,\n public readonly time: Date,\n ) {}\n}\n","import { tick, type Cmd, type Msg, FocusMsg, BlurMsg } from '@boba-cli/tea'\nimport { Style } from '@boba-cli/chapstick'\nimport { BlinkMsg, InitialBlinkMsg } from './messages.js'\n\nconst defaultBlinkSpeed = 530 // ms\n\n// Module-level ID counter for unique cursor identification\nlet lastId = 0\nfunction nextId(): number {\n return ++lastId\n}\n\n/** Available cursor behaviors. @public */\nexport enum CursorMode {\n Blink = 'blink',\n Static = 'static',\n Hidden = 'hidden',\n}\n\n/** Options for creating a CursorModel. @public */\nexport interface CursorOptions {\n blinkSpeed?: number\n style?: Style\n textStyle?: Style\n char?: string\n mode?: CursorMode\n focused?: boolean\n}\n\ntype CursorState = {\n id: number\n tag: number\n blink: boolean\n focus: boolean\n mode: CursorMode\n}\n\n/**\n * Cursor component model.\n *\n * Use `tickBlink()` to start blinking, or handle `InitialBlinkMsg` from `initBlink()`.\n * Handle `BlinkMsg`, `FocusMsg`, and `BlurMsg` in your update loop.\n *\n * @public\n */\nexport class CursorModel {\n readonly blinkSpeed: number\n readonly style: Style\n readonly textStyle: Style\n readonly char: string\n readonly #id: number\n readonly #tag: number\n readonly #blink: boolean\n readonly #focus: boolean\n readonly #mode: CursorMode\n\n constructor(options: CursorOptions = {}, state?: CursorState) {\n this.blinkSpeed = options.blinkSpeed ?? defaultBlinkSpeed\n this.style = options.style ?? new Style()\n this.textStyle = options.textStyle ?? new Style()\n this.char = options.char ?? ' '\n this.#id = state?.id ?? nextId()\n this.#tag = state?.tag ?? 0\n this.#blink = state?.blink ?? true\n this.#focus = state?.focus ?? false\n this.#mode = state?.mode ?? options.mode ?? CursorMode.Blink\n }\n\n /** Unique ID for this cursor (for message routing). */\n id(): number {\n return this.#id\n }\n\n /** Current cursor mode. */\n mode(): CursorMode {\n return this.#mode\n }\n\n /** Whether the cursor is currently in the \"text\" state (hidden block). */\n isBlinkHidden(): boolean {\n return this.#blink\n }\n\n /** Whether this cursor currently has focus. */\n isFocused(): boolean {\n return this.#focus\n }\n\n /** Kick off blinking at init (emits InitialBlinkMsg). */\n initBlink(): Cmd<InitialBlinkMsg> {\n return () => new InitialBlinkMsg()\n }\n\n /** Command to schedule the next blink toggle. */\n tickBlink(): [CursorModel, Cmd<BlinkMsg>] {\n const nextTag = this.#tag + 1\n const id = this.#id\n const speed = this.blinkSpeed\n const next = this.#withState({ ...this.#state(), tag: nextTag })\n const cmd: Cmd<BlinkMsg> = tick(\n speed,\n (time: Date) => new BlinkMsg(id, nextTag, time),\n )\n return [next, cmd]\n }\n\n /** Set the character under the cursor. */\n withChar(char: string): CursorModel {\n return this.#withState(this.#state(), { char })\n }\n\n /** Set the cursor mode. Returns a new model and optional blink command. */\n withMode(mode: CursorMode): [CursorModel, Cmd<Msg>] {\n const bounded =\n mode === CursorMode.Blink ||\n mode === CursorMode.Static ||\n mode === CursorMode.Hidden\n ? mode\n : CursorMode.Blink\n\n const blink =\n bounded === CursorMode.Hidden || !this.#focus ? true : this.#blink\n const next = this.#withState({ ...this.#state(), blink, mode: bounded })\n\n if (bounded === CursorMode.Blink && this.#focus) {\n const [scheduled, cmd] = next.tickBlink()\n return [scheduled, cmd]\n }\n return [next, null]\n }\n\n /** Focus the cursor. Returns new model and optional blink command. */\n focus(): [CursorModel, Cmd<Msg>] {\n const blink = this.#mode === CursorMode.Hidden ? true : this.#blink\n const next = this.#withState({ ...this.#state(), blink, focus: true })\n if (this.#mode === CursorMode.Blink) {\n const [scheduled, cmd] = next.tickBlink()\n return [scheduled, cmd]\n }\n return [next, null]\n }\n\n /** Blur the cursor. */\n blur(): CursorModel {\n return this.#withState({ ...this.#state(), blink: true, focus: false })\n }\n\n /**\n * Update the cursor model with an incoming message.\n * Returns a new model and an optional command.\n */\n update(msg: Msg): [CursorModel, Cmd<Msg>] {\n if (msg instanceof InitialBlinkMsg) {\n if (this.#mode !== CursorMode.Blink || !this.#focus) {\n return [this, null]\n }\n const [next, cmd] = this.tickBlink()\n return [next, cmd as Cmd<Msg>]\n }\n\n if (msg instanceof FocusMsg) {\n return this.focus()\n }\n\n if (msg instanceof BlurMsg) {\n const next = this.blur()\n return [next, null]\n }\n\n if (msg instanceof BlinkMsg) {\n // Is this model blink-able and expecting this tick?\n if (this.#mode !== CursorMode.Blink || !this.#focus) {\n return [this, null]\n }\n if (msg.id !== this.#id || msg.tag !== this.#tag) {\n return [this, null]\n }\n\n const toggled = this.#withState({ ...this.#state(), blink: !this.#blink })\n const [next, cmd] = toggled.tickBlink()\n return [next, cmd as Cmd<Msg>]\n }\n\n return [this, null]\n }\n\n /** Render the cursor. */\n view(): string {\n const char = this.char\n if (this.#blink) {\n return this.textStyle.inline(true).render(char)\n }\n return this.style.inline(true).render(char)\n }\n\n #state(): CursorState {\n return {\n id: this.#id,\n tag: this.#tag,\n blink: this.#blink,\n focus: this.#focus,\n mode: this.#mode,\n }\n }\n\n #withState(state: CursorState, overrides?: { char?: string }): CursorModel {\n return new CursorModel(\n {\n blinkSpeed: this.blinkSpeed,\n style: this.style,\n textStyle: this.textStyle,\n char: overrides?.char ?? this.char,\n mode: state.mode,\n focused: state.focus,\n },\n state,\n )\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@boba-cli/cursor",
|
|
3
|
+
"description": "Cursor component for Boba terminal UIs",
|
|
4
|
+
"version": "0.1.0-alpha.2",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"@boba-cli/chapstick": "0.1.0-alpha.2",
|
|
7
|
+
"@boba-cli/tea": "0.1.0-alpha.1"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"typescript": "5.8.2",
|
|
11
|
+
"vitest": "^4.0.16"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20.0.0"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"import": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"require": {
|
|
23
|
+
"types": "./dist/index.d.cts",
|
|
24
|
+
"default": "./dist/index.cjs"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"main": "./dist/index.cjs",
|
|
33
|
+
"module": "./dist/index.js",
|
|
34
|
+
"type": "module",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"check:api-report": "pnpm run generate:api-report",
|
|
39
|
+
"check:eslint": "pnpm run lint",
|
|
40
|
+
"generate:api-report": "api-extractor run --local",
|
|
41
|
+
"lint": "eslint \"{src,test}/**/*.{ts,tsx}\"",
|
|
42
|
+
"test": "vitest run"
|
|
43
|
+
}
|
|
44
|
+
}
|