@vincemakes/kiso-tui 0.25.0 → 0.26.1

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.
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The SESSION PICKER's input controller — S5 (finding C10). TUI2-R2 ②:
3
+ * the band's third occupant. Its filter is the @ picker's rank aimed at
4
+ * the session id; this controller owns the keys, the compositor draws
5
+ * the rows.
6
+ *
7
+ * The picker is MODAL in a way the @ picker is not: it opens before a
8
+ * session exists, owns the whole composer (the buffer IS the filter
9
+ * query, derived on every read rather than stored, so every buffer op —
10
+ * backspace, the kills, paste — filters correctly with no handler of
11
+ * its own), and the only ways out are a pick and an esc. That is why
12
+ * the commit callback lives here rather than on the line channel: the
13
+ * caller is waiting for an id, not for a turn.
14
+ *
15
+ * DECLARED MOVE (S5, 2026-09-06): `beginPick`, `#pickView`, `pickState`,
16
+ * `#pickUp`, `#pickRows`, `#pickClose`, `#pickAccept` and the ↑↓ branch
17
+ * stood in editor.ts; the bodies are the same, the buffer access goes
18
+ * through the host.
19
+ */
20
+ import { AT_VISIBLE } from "./at-picker.js";
21
+ import { sessionFilter } from "./session-picker.js";
22
+ export class PickInput {
23
+ host;
24
+ #cards = null;
25
+ #commit = null;
26
+ #sel = 0;
27
+ constructor(host) {
28
+ this.host = host;
29
+ }
30
+ up() {
31
+ return this.#cards !== null;
32
+ }
33
+ /** Open the picker on a bound card source. The composer is cleared
34
+ * (the buffer becomes the filter query) and `onPick` receives the
35
+ * chosen id — or null when the human leaves without picking, which
36
+ * is a first-class outcome and not an error. */
37
+ begin(cards, onPick) {
38
+ this.#cards = cards;
39
+ this.#commit = onPick;
40
+ this.#sel = 0;
41
+ this.host.syncMouse();
42
+ this.host.clear();
43
+ this.host.reflow();
44
+ this.host.render();
45
+ }
46
+ /** The picker's state, derived: the full card list (the id column
47
+ * measures over ALL of them, so the columns never jump), the
48
+ * filtered matches, and the selection CLAMPED at read time — the
49
+ * same correction discipline the @ picker uses, for the same
50
+ * reason: narrowing can only ever shrink the list. */
51
+ state() {
52
+ if (this.#cards === null)
53
+ return null;
54
+ const cards = this.#cards();
55
+ const matches = sessionFilter(cards, this.host.line());
56
+ return { cards, matches, selected: Math.max(0, Math.min(this.#sel, matches.length - 1)) };
57
+ }
58
+ /** The band's height estimate: the header + the windowed rows (or
59
+ * the one "no match" row) + the counter. */
60
+ rows() {
61
+ const view = this.state();
62
+ return view === null ? 0 : Math.min(Math.max(view.matches.length, 1), AT_VISIBLE) + 2;
63
+ }
64
+ /** ↑↓: the selection walks the matches and stops at both ends. True
65
+ * when the picker owned the key (the caller renders). */
66
+ arrow(dir) {
67
+ const view = this.state();
68
+ if (view === null)
69
+ return false;
70
+ this.#sel = dir === "up" ? Math.max(0, view.selected - 1) : Math.min(Math.max(0, view.matches.length - 1), view.selected + 1);
71
+ return true;
72
+ }
73
+ /** Close and hand the verdict back. The callback fires AFTER the
74
+ * state is cleared, so a caller that re-enters (a second picker, a
75
+ * session that starts) never sees the closing picker's rows. */
76
+ close(id) {
77
+ const cb = this.#commit;
78
+ this.#cards = null;
79
+ this.#commit = null;
80
+ this.#sel = 0;
81
+ this.host.syncMouse();
82
+ this.host.clear();
83
+ this.host.reflow();
84
+ cb?.(id);
85
+ this.host.render();
86
+ }
87
+ /** Enter takes the SELECTED session. An empty match set takes
88
+ * nothing and leaves the picker up: a picker that invented a pick
89
+ * when the query matched nothing would resume the wrong session,
90
+ * which is the one failure this surface must never have. */
91
+ accept() {
92
+ const view = this.state();
93
+ if (view === null)
94
+ return;
95
+ const card = view.matches[view.selected];
96
+ if (card === undefined)
97
+ return;
98
+ this.close(card.id);
99
+ }
100
+ }
@@ -23,7 +23,7 @@
23
23
  * state — which is what lets the picker band and the `kiso sessions`
24
24
  * listing render from ONE definition instead of two that drift.
25
25
  */
26
- import { escapeTerminal, palette } from "./render.js";
26
+ import { escapeTerminal, palette } from "./lines.js";
27
27
  import { selectionBar, visibleWidth, widthCut } from "./components.js";
28
28
  import { atEmbed, bandHeader, longestRun, AT_VISIBLE, atWindow } from "./at-picker.js";
29
29
  /** The glyph per state — one cell each, so the badge column never
package/dist/status.js CHANGED
@@ -21,7 +21,7 @@
21
21
  * deliberate exception: the running row's interrupt hint, which KC2 §2
22
22
  * widens to name the new gesture.
23
23
  */
24
- import { kUnit } from "./render.js";
24
+ import { kUnit } from "./lines.js";
25
25
  import { TWINKLE } from "@vincemakes/kiso-tui-cells/render";
26
26
  /**
27
27
  * R3 (design §5.2) — the working glyph family is the TWINKLE, and the
@@ -33,8 +33,8 @@
33
33
  * so a viewer that expanded a committed cell in place would corrupt the
34
34
  * window arithmetic for the rest of the session.
35
35
  */
36
- import { palette } from "./render.js";
37
- import { visibleWidth } from "./components.js";
36
+ import { palette } from "./lines.js";
37
+ import { cutLine, visibleWidth } from "./components.js";
38
38
  /** The gutter every viewer row carries: the cursor mark and its space. */
39
39
  export const VIEWER_GUTTER = 3;
40
40
  export function viewerInit(entries) {
@@ -135,7 +135,7 @@ export function viewerRows(entries, state, W, rows) {
135
135
  const mark = line.head ? (open ? "▾" : onCursor ? "▸" : " ") : "│";
136
136
  const gutter = onCursor ? `${p.bold}${mark}${p.reset} ` : `${p.dim}${mark}${p.reset} `;
137
137
  const row = ` ${gutter}${line.text}`;
138
- out.push(open ? shade(row, W) : cut(row, W));
138
+ out.push(open ? shade(row, W) : cutLine(row, W));
139
139
  }
140
140
  return out;
141
141
  }
@@ -152,34 +152,11 @@ export function viewerRows(entries, state, W, rows) {
152
152
  */
153
153
  function shade(row, W) {
154
154
  const p = palette();
155
- const body = cut(row, W);
155
+ const body = cutLine(row, W);
156
156
  if (p.wash === "")
157
157
  return body;
158
158
  return `${p.wash}${body}${" ".repeat(Math.max(0, W - visibleWidth(body)))}${p.washEnd}`;
159
159
  }
160
- /** The last resort — the row is cut at W rather than overflowing it. */
161
- function cut(row, W) {
162
- if (visibleWidth(row) <= W)
163
- return row;
164
- let out = "";
165
- let n = 0;
166
- for (let i = 0; i < row.length;) {
167
- if (row[i] === "\x1b") {
168
- const j = row.indexOf("m", i);
169
- if (j < 0)
170
- break;
171
- out += row.slice(i, j + 1);
172
- i = j + 1;
173
- continue;
174
- }
175
- if (n >= W - 1)
176
- break;
177
- out += row[i];
178
- n += 1;
179
- i += 1;
180
- }
181
- return `${out}…${palette().reset}`;
182
- }
183
160
  /** The viewer's affordance row — what the keys do, where they are
184
161
  * useful (the PICKER_HINT convention). */
185
162
  export function viewerHint(state, entries) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tui",
3
- "version": "0.25.0",
3
+ "version": "0.26.1",
4
4
  "description": "kiso tui — the pure terminal layer (cell renderer, dock, raw editor, diff, palette). Zero runtime dependencies: input is data, output is bytes.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -35,6 +35,6 @@
35
35
  },
36
36
  "homepage": "https://github.com/vincemakes/kiso/tree/main/packages/tui#readme",
37
37
  "dependencies": {
38
- "@vincemakes/kiso-tui-cells": "0.25.0"
38
+ "@vincemakes/kiso-tui-cells": "0.26.1"
39
39
  }
40
40
  }
File without changes