@sigrun/tui 0.1.1 → 0.2.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.
Files changed (108) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/package.json +2 -2
  3. package/dist/actions.d.ts +0 -119
  4. package/dist/actions.js +0 -12
  5. package/dist/api.d.ts +0 -152
  6. package/dist/api.js +0 -189
  7. package/dist/blocks/braille.d.ts +0 -41
  8. package/dist/blocks/braille.js +0 -131
  9. package/dist/blocks/chart.d.ts +0 -20
  10. package/dist/blocks/chart.js +0 -397
  11. package/dist/blocks/collection/calendar.d.ts +0 -28
  12. package/dist/blocks/collection/calendar.js +0 -232
  13. package/dist/blocks/collection/card.d.ts +0 -36
  14. package/dist/blocks/collection/card.js +0 -128
  15. package/dist/blocks/collection/collection.d.ts +0 -9
  16. package/dist/blocks/collection/collection.js +0 -26
  17. package/dist/blocks/collection/grid.d.ts +0 -18
  18. package/dist/blocks/collection/grid.js +0 -72
  19. package/dist/blocks/collection/lanes.d.ts +0 -32
  20. package/dist/blocks/collection/lanes.js +0 -320
  21. package/dist/blocks/collection/list.d.ts +0 -30
  22. package/dist/blocks/collection/list.js +0 -273
  23. package/dist/blocks/collection/move.d.ts +0 -67
  24. package/dist/blocks/collection/move.js +0 -96
  25. package/dist/blocks/collection/shared.d.ts +0 -87
  26. package/dist/blocks/collection/shared.js +0 -177
  27. package/dist/blocks/collection/table.d.ts +0 -26
  28. package/dist/blocks/collection/table.js +0 -221
  29. package/dist/blocks/content.d.ts +0 -48
  30. package/dist/blocks/content.js +0 -253
  31. package/dist/blocks/context.d.ts +0 -96
  32. package/dist/blocks/context.js +0 -65
  33. package/dist/blocks/diff.d.ts +0 -21
  34. package/dist/blocks/diff.js +0 -25
  35. package/dist/blocks/ext-transport.d.ts +0 -70
  36. package/dist/blocks/ext-transport.js +0 -165
  37. package/dist/blocks/form/choice.d.ts +0 -54
  38. package/dist/blocks/form/choice.js +0 -175
  39. package/dist/blocks/form/fields.d.ts +0 -56
  40. package/dist/blocks/form/fields.js +0 -136
  41. package/dist/blocks/form/file.d.ts +0 -61
  42. package/dist/blocks/form/file.js +0 -121
  43. package/dist/blocks/form/form.d.ts +0 -53
  44. package/dist/blocks/form/form.js +0 -703
  45. package/dist/blocks/form/list.d.ts +0 -37
  46. package/dist/blocks/form/list.js +0 -70
  47. package/dist/blocks/form/values.d.ts +0 -20
  48. package/dist/blocks/form/values.js +0 -104
  49. package/dist/blocks/icons.d.ts +0 -14
  50. package/dist/blocks/icons.js +0 -74
  51. package/dist/blocks/image.d.ts +0 -39
  52. package/dist/blocks/image.js +0 -82
  53. package/dist/blocks/layout.d.ts +0 -31
  54. package/dist/blocks/layout.js +0 -155
  55. package/dist/blocks/log.d.ts +0 -24
  56. package/dist/blocks/log.js +0 -111
  57. package/dist/blocks/meter.d.ts +0 -16
  58. package/dist/blocks/meter.js +0 -50
  59. package/dist/blocks/page.d.ts +0 -54
  60. package/dist/blocks/page.js +0 -355
  61. package/dist/blocks/render.d.ts +0 -31
  62. package/dist/blocks/render.js +0 -145
  63. package/dist/blocks/surface-view.d.ts +0 -33
  64. package/dist/blocks/surface-view.js +0 -86
  65. package/dist/blocks/surface.d.ts +0 -75
  66. package/dist/blocks/surface.js +0 -468
  67. package/dist/blocks/tones.d.ts +0 -23
  68. package/dist/blocks/tones.js +0 -63
  69. package/dist/blocks/treeview.d.ts +0 -22
  70. package/dist/blocks/treeview.js +0 -121
  71. package/dist/blocks/width.d.ts +0 -32
  72. package/dist/blocks/width.js +0 -70
  73. package/dist/clipboard.d.ts +0 -20
  74. package/dist/clipboard.js +0 -19
  75. package/dist/editor-launch.d.ts +0 -34
  76. package/dist/editor-launch.js +0 -70
  77. package/dist/keymaps/emacs.d.ts +0 -18
  78. package/dist/keymaps/emacs.js +0 -172
  79. package/dist/keymaps/standard.d.ts +0 -17
  80. package/dist/keymaps/standard.js +0 -175
  81. package/dist/keymaps/types.d.ts +0 -43
  82. package/dist/keymaps/types.js +0 -28
  83. package/dist/keymaps/vim.d.ts +0 -48
  84. package/dist/keymaps/vim.js +0 -291
  85. package/dist/launch.d.ts +0 -149
  86. package/dist/launch.js +0 -446
  87. package/dist/sse.d.ts +0 -109
  88. package/dist/sse.js +0 -401
  89. package/dist/theme.d.ts +0 -51
  90. package/dist/theme.js +0 -119
  91. package/dist/ui/ambient.d.ts +0 -81
  92. package/dist/ui/ambient.js +0 -301
  93. package/dist/ui/app.d.ts +0 -245
  94. package/dist/ui/app.js +0 -1325
  95. package/dist/ui/composer.d.ts +0 -66
  96. package/dist/ui/composer.js +0 -125
  97. package/dist/ui/conversation-pane.d.ts +0 -123
  98. package/dist/ui/conversation-pane.js +0 -463
  99. package/dist/ui/help.d.ts +0 -15
  100. package/dist/ui/help.js +0 -80
  101. package/dist/ui/interactions.d.ts +0 -90
  102. package/dist/ui/interactions.js +0 -330
  103. package/dist/ui/picker.d.ts +0 -26
  104. package/dist/ui/picker.js +0 -130
  105. package/dist/ui/sidebar.d.ts +0 -99
  106. package/dist/ui/sidebar.js +0 -269
  107. package/dist/ui/status-bar.d.ts +0 -77
  108. package/dist/ui/status-bar.js +0 -146
@@ -1,232 +0,0 @@
1
- /**
2
- * The `calendar` display (UI-blocks U2 Task 4, spec §5.5 + §12.3 +
3
- * §12.4): a month grid at regular/wide (day-rows — the same v1 shape
4
- * as U1's grid, not a weekday-aligned matrix); compact shows the
5
- * agenda (§12.3), as does `time.range: "agenda"` at any width.
6
- *
7
- * Keyboard (§12.4): `[`/`]` shift the visible month, `t` returns to
8
- * today — all view-only, nothing dispatches. `m` on an event picks it
9
- * up; arrows move it by DAY; Enter drops, dispatching `onMove` with
10
- * `{itemId, to: {start, end}}` — the duration preserved by shifting
11
- * start AND end by the day delta (the same payload as U1's pointer
12
- * drop). Esc cancels.
13
- *
14
- * The v1 TUI calendar has no pointer create/resize gestures (`onCreate`
15
- * and `onResize` stay U1-pointer-only affordances; noted in the task
16
- * report).
17
- */
18
- import { truncate, widthClassOf } from "../width.js";
19
- import { cardLines } from "./card.js";
20
- import { CollectionState, collectionItems, dispatchActivate, sortItems, } from "./shared.js";
21
- /** The day key (YYYY-MM-DD) an ISO timestamp falls on. */
22
- function dayKey(iso) {
23
- return iso.slice(0, 10);
24
- }
25
- /** Shift a day key by `days`. */
26
- function shiftDay(key, days) {
27
- const date = new Date(`${key}T00:00:00Z`);
28
- date.setUTCDate(date.getUTCDate() + days);
29
- return date.toISOString().slice(0, 10);
30
- }
31
- /** Shift an ISO instant by whole days, keeping its clock time. */
32
- function shiftInstant(iso, days) {
33
- const date = new Date(iso);
34
- date.setUTCDate(date.getUTCDate() + days);
35
- return date.toISOString().replace(/\.\d{3}Z$/, "Z");
36
- }
37
- /** Today's month key (view-range default; fake timers drive tests). */
38
- function currentMonth() {
39
- return new Date().toISOString().slice(0, 7);
40
- }
41
- /** Shift a month key by `months`. */
42
- function shiftMonth(key, months) {
43
- const [yearRaw, monthRaw] = key.split("-");
44
- const year = Number(yearRaw);
45
- const month = Number(monthRaw) - 1 + months;
46
- return new Date(Date.UTC(year, month, 1)).toISOString().slice(0, 7);
47
- }
48
- /** The days a month grid renders (1st through the month's end). */
49
- function daysInMonth(month) {
50
- const [yearRaw, monthRaw] = month.split("-");
51
- const year = Number(yearRaw);
52
- const m = Number(monthRaw);
53
- const days = [];
54
- const date = new Date(Date.UTC(year, m - 1, 1));
55
- while (date.getUTCMonth() === m - 1) {
56
- days.push(date.toISOString().slice(0, 10));
57
- date.setUTCDate(date.getUTCDate() + 1);
58
- }
59
- return days;
60
- }
61
- /** An item's start/end ISO strings under the block's `time` config. */
62
- function itemRange(block, item) {
63
- const time = block.time;
64
- if (time === undefined)
65
- return undefined;
66
- if (typeof item !== "object" || item === null || Array.isArray(item))
67
- return undefined;
68
- const record = item;
69
- const start = record[time.start];
70
- if (typeof start !== "string")
71
- return undefined;
72
- const end = time.end === undefined ? undefined : record[time.end];
73
- return { start, end: typeof end === "string" ? end : undefined };
74
- }
75
- /** The calendar display. */
76
- export class CalendarComponent {
77
- #block;
78
- #ctx;
79
- #state = new CollectionState();
80
- #tick = 0;
81
- /** The visible month (view-only; `[`/`]`/`t` shift it). */
82
- #month = currentMonth();
83
- /** The agenda cursor (a flat item index over the agenda order). */
84
- #cursor = 0;
85
- /** The picked-up event (calendar moves use a day placement, not the
86
- * lanes' group/index placement — the session wrapper tracks it). */
87
- #held;
88
- constructor(block, ctx) {
89
- this.#block = block;
90
- this.#ctx = ctx;
91
- }
92
- /** Every item with a parseable time range, in start order. */
93
- #events() {
94
- const rows = [];
95
- for (const row of sortItems(collectionItems(this.#block, this.#ctx), this.#block.sort)) {
96
- const range = itemRange(this.#block, row.item);
97
- if (range === undefined)
98
- continue;
99
- rows.push({ row, start: range.start, end: range.end });
100
- }
101
- rows.sort((a, b) => a.start.localeCompare(b.start));
102
- return rows;
103
- }
104
- render(width) {
105
- this.#tick += 1;
106
- const compact = widthClassOf(width) === "compact" || this.#block.time?.range === "agenda";
107
- const events = this.#events();
108
- if (compact)
109
- return this.#agenda(width, events);
110
- return this.#monthGrid(width, events);
111
- }
112
- /** The agenda: items sorted by start, one row each. */
113
- #agenda(width, events) {
114
- const lines = [this.#ctx.theme.dim(truncate(this.#month, width))];
115
- events.forEach((event, index) => {
116
- const cursor = index === this.#cursor ? "> " : " ";
117
- const held = this.#held?.itemId === event.row.id;
118
- const mark = held ? (this.#ctx.unicode ? "✋ " : "* ") : "";
119
- const card = cardLines(this.#block.item, event.row.item, this.#ctx, width - cursor.length - mark.length - 12, 0, this.#tick);
120
- const when = `${event.start.slice(0, 10)} `;
121
- const [first, ...rest] = card;
122
- lines.push(truncate(`${cursor}${mark}${when}${first ?? ""}`, width));
123
- for (const line of rest) {
124
- lines.push(truncate(`${cursor}${" ".repeat(mark.length + when.length)}${line}`, width));
125
- }
126
- });
127
- return lines;
128
- }
129
- /** The month grid: one row per day, events under their day. */
130
- #monthGrid(width, events) {
131
- const byDay = new Map();
132
- for (const event of events) {
133
- const day = dayKey(event.start);
134
- const list = byDay.get(day) ?? [];
135
- list.push(event);
136
- byDay.set(day, list);
137
- }
138
- const lines = [this.#ctx.theme.bold(truncate(this.#month, width))];
139
- for (const day of daysInMonth(this.#month)) {
140
- const todays = byDay.get(day) ?? [];
141
- const heldHere = this.#held !== undefined && this.#held.at === day;
142
- const marker = heldHere ? (this.#ctx.unicode ? " ⌄" : " v") : "";
143
- lines.push(truncate(`${day.slice(8)}${marker}`, width));
144
- for (const event of todays) {
145
- const held = this.#held?.itemId === event.row.id;
146
- const mark = held ? (this.#ctx.unicode ? "✋ " : "* ") : "";
147
- const card = cardLines(this.#block.item, event.row.item, this.#ctx, width - 2 - mark.length, 0, this.#tick);
148
- const [first, ...rest] = card;
149
- lines.push(truncate(` ${mark}${first ?? ""}`, width));
150
- for (const line of rest) {
151
- lines.push(truncate(` ${" ".repeat(mark.length)}${line}`, width));
152
- }
153
- }
154
- }
155
- return lines;
156
- }
157
- handleInput(data) {
158
- // Range keys are always view-only (§12.4).
159
- if (data === "[") {
160
- this.#month = shiftMonth(this.#month, -1);
161
- return;
162
- }
163
- if (data === "]") {
164
- this.#month = shiftMonth(this.#month, 1);
165
- return;
166
- }
167
- if (data === "t") {
168
- this.#month = currentMonth();
169
- return;
170
- }
171
- const events = this.#events();
172
- // A pick-up in flight: arrows move the event by DAY.
173
- const held = this.#held;
174
- if (held !== undefined) {
175
- if (data === "\x1b") {
176
- this.#held = undefined;
177
- return;
178
- }
179
- let delta = 0;
180
- if (data === "\x1b[C" || data === "\x1b[B")
181
- delta = 1;
182
- if (data === "\x1b[D" || data === "\x1b[A")
183
- delta = -1;
184
- if (delta !== 0) {
185
- this.#held = { ...held, at: shiftDay(held.at, delta) };
186
- return;
187
- }
188
- if (data === "\r") {
189
- this.#held = undefined;
190
- const days = (Date.parse(`${held.at}T00:00:00Z`) -
191
- Date.parse(`${dayKey(held.start)}T00:00:00Z`)) /
192
- 86_400_000;
193
- if (days === 0)
194
- return;
195
- const onMove = this.#block.onMove;
196
- if (onMove === undefined)
197
- return;
198
- const start = shiftInstant(held.start, days);
199
- const end = held.end === undefined ? undefined : shiftInstant(held.end, days);
200
- const to = end === undefined ? { start } : { start, end };
201
- this.#ctx.dispatch({ type: "move", name: onMove }, { itemId: held.itemId, to });
202
- }
203
- return;
204
- }
205
- if (this.#cursor >= events.length)
206
- this.#cursor = Math.max(0, events.length - 1);
207
- const current = events[this.#cursor];
208
- if (data === "\x1b[B") {
209
- this.#cursor = Math.min(events.length - 1, this.#cursor + 1);
210
- return;
211
- }
212
- if (data === "\x1b[A") {
213
- this.#cursor = Math.max(0, this.#cursor - 1);
214
- return;
215
- }
216
- if (data === "m" && current !== undefined && this.#block.onMove !== undefined) {
217
- this.#held = {
218
- itemId: current.row.id,
219
- start: current.start,
220
- end: current.end,
221
- at: dayKey(current.start),
222
- };
223
- return;
224
- }
225
- if (data === "\r" && current !== undefined) {
226
- dispatchActivate(this.#block, this.#ctx, current.row);
227
- }
228
- }
229
- invalidate() {
230
- // State (range, cursor, the pick-up) survives re-renders.
231
- }
232
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * The card template (UI-blocks spec §5.5.1) as terminal lines: a FIXED
3
- * line layout, every slot optional except `title`, each slot resolved
4
- * against the item (`./` pointers).
5
- *
6
- * Layout, top to bottom:
7
- * 1. eyebrow line (dimmed small text)
8
- * 2. title line: icon glyph, tone marker, title (title carries the
9
- * tone's colour through TuiTheme)
10
- * 3. subtitle (one line)
11
- * 4. body, wrapped and clamped to 4 lines (spec: lists and lanes clamp;
12
- * the TUI clamps in every display — a 100-cell grid card cannot
13
- * afford a full body either, and `detail` panes are Task 4's)
14
- * 5. badges + meta on ONE line (badges first, then meta facts)
15
- * 6. metrics, one per line (`label: value`, `formatValue` formatted)
16
- * 7. status: spinner frame (when `spinner` binds true) + tone marker +
17
- * label — the frame ticks per render (`spinnerFrame`)
18
- * 8. deps as `after: id, id, …`
19
- *
20
- * The card `tone` marker also leads the title; with no tone binding the
21
- * marker is the neutral one so lines align.
22
- */
23
- import type { Card, JsonValue } from "@sigrun/shared/blocks";
24
- import { type BlockCtx } from "../context.js";
25
- /** The body clamp (spec §5.5.1: 4 lines in lists and lanes). */
26
- export declare const CARD_BODY_LINES = 4;
27
- /** The spinner frame for render `tick` (a render-indexed clock: the TUI
28
- * has no card-local timers, so a spinning status advances as renders
29
- * arrive — the same convention ambient rows use). */
30
- export declare function spinnerFrame(tick: number, ascii: boolean): string;
31
- /**
32
- * The card's lines for `item` at `width`, indented `indent` cells.
33
- * `tick` indexes the spinner frame (the owning component's render
34
- * count). Lines are display-width truncated, never wider than the pane.
35
- */
36
- export declare function cardLines(card: Card, item: JsonValue, ctx: BlockCtx, width: number, indent: number, tick: number): string[];
@@ -1,128 +0,0 @@
1
- /**
2
- * The card template (UI-blocks spec §5.5.1) as terminal lines: a FIXED
3
- * line layout, every slot optional except `title`, each slot resolved
4
- * against the item (`./` pointers).
5
- *
6
- * Layout, top to bottom:
7
- * 1. eyebrow line (dimmed small text)
8
- * 2. title line: icon glyph, tone marker, title (title carries the
9
- * tone's colour through TuiTheme)
10
- * 3. subtitle (one line)
11
- * 4. body, wrapped and clamped to 4 lines (spec: lists and lanes clamp;
12
- * the TUI clamps in every display — a 100-cell grid card cannot
13
- * afford a full body either, and `detail` panes are Task 4's)
14
- * 5. badges + meta on ONE line (badges first, then meta facts)
15
- * 6. metrics, one per line (`label: value`, `formatValue` formatted)
16
- * 7. status: spinner frame (when `spinner` binds true) + tone marker +
17
- * label — the frame ticks per render (`spinnerFrame`)
18
- * 8. deps as `after: id, id, …`
19
- *
20
- * The card `tone` marker also leads the title; with no tone binding the
21
- * marker is the neutral one so lines align.
22
- */
23
- import { formatValue } from "@sigrun/shared/blocks";
24
- import { bindRaw, bindText, bindTone } from "../context.js";
25
- import { iconGlyph } from "../icons.js";
26
- import { toneFg, toneMarker } from "../tones.js";
27
- import { truncate, wrapPlain } from "../width.js";
28
- /** The body clamp (spec §5.5.1: 4 lines in lists and lanes). */
29
- export const CARD_BODY_LINES = 4;
30
- /** The braille spinner frames (ASCII: the classic four-bar cycle). */
31
- const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
32
- const SPINNER_FRAMES_ASCII = ["*", "+", "x", "+"];
33
- /** The spinner frame for render `tick` (a render-indexed clock: the TUI
34
- * has no card-local timers, so a spinning status advances as renders
35
- * arrive — the same convention ambient rows use). */
36
- export function spinnerFrame(tick, ascii) {
37
- const frames = ascii ? SPINNER_FRAMES_ASCII : SPINNER_FRAMES;
38
- return frames[tick % frames.length] ?? frames[0] ?? "*";
39
- }
40
- /** One meta entry resolved to its display text ("" = omit). */
41
- function metaText(entry, ctx, item) {
42
- if ("person" in entry) {
43
- // The extension supplies the display name; `user` is the handle
44
- // and is not shown (spec §5.5.1).
45
- return bindText(entry.person.name, ctx);
46
- }
47
- if ("time" in entry) {
48
- const at = bindRaw(entry.time, ctx);
49
- return at === undefined ? "" : formatValue(at, entry.format, ctx.locale, ctx.timeZone);
50
- }
51
- return bindText(entry.text, ctx);
52
- }
53
- /**
54
- * The card's lines for `item` at `width`, indented `indent` cells.
55
- * `tick` indexes the spinner frame (the owning component's render
56
- * count). Lines are display-width truncated, never wider than the pane.
57
- */
58
- export function cardLines(card, item, ctx, width, indent, tick) {
59
- const pad = " ".repeat(indent);
60
- const inner = Math.max(1, width - indent);
61
- const itemCtx = { ...ctx, item };
62
- const lines = [];
63
- const clamp = (line) => truncate(line, inner);
64
- // 1. eyebrow
65
- const eyebrow = bindText(card.eyebrow, itemCtx);
66
- if (eyebrow !== "")
67
- lines.push(pad + clamp(ctx.theme.dim(eyebrow)));
68
- // 2. title: icon, tone marker, toned title text.
69
- const tone = bindTone(card.tone, itemCtx);
70
- const title = bindText(card.title, itemCtx);
71
- const icon = card.icon === undefined ? "" : `${iconGlyph(bindText(card.icon, itemCtx), !ctx.unicode)} `;
72
- const marker = `${toneMarker(tone, !ctx.unicode)} `;
73
- lines.push(pad + clamp(`${icon}${marker}${toneFg(ctx.theme, tone)(title)}`));
74
- // 3. subtitle
75
- const subtitle = bindText(card.subtitle, itemCtx);
76
- if (subtitle !== "")
77
- lines.push(pad + clamp(subtitle));
78
- // 4. body, clamped to CARD_BODY_LINES.
79
- const body = bindText(card.body, itemCtx);
80
- if (body !== "") {
81
- for (const line of wrapPlain(body, inner).slice(0, CARD_BODY_LINES)) {
82
- lines.push(pad + line);
83
- }
84
- }
85
- // 5. badges + meta, one line.
86
- const parts = [];
87
- for (const badge of card.badges ?? []) {
88
- const text = bindText(badge.text, itemCtx);
89
- if (text === "")
90
- continue;
91
- const badgeTone = bindTone(badge.tone, itemCtx);
92
- parts.push(`[${toneFg(ctx.theme, badgeTone)(text)}]`);
93
- }
94
- const metaParts = (card.meta ?? [])
95
- .map((entry) => metaText(entry, itemCtx, item))
96
- .filter((text) => text !== "");
97
- if (metaParts.length > 0)
98
- parts.push(metaParts.join(" · "));
99
- if (parts.length > 0)
100
- lines.push(pad + clamp(parts.join(" ")));
101
- // 6. metrics, one per line.
102
- for (const metric of card.metrics ?? []) {
103
- const label = bindText(metric.label, itemCtx);
104
- const value = bindRaw(metric.value, itemCtx);
105
- if (label === "" || value === undefined)
106
- continue;
107
- lines.push(pad + clamp(`${label}: ${formatValue(value, metric.format, ctx.locale, ctx.timeZone)}`));
108
- }
109
- // 7. status (spinner frame + tone marker + label).
110
- if (card.status !== undefined) {
111
- const label = bindText(card.status.label, itemCtx);
112
- if (label !== "") {
113
- const statusTone = bindTone(card.status.tone, itemCtx);
114
- const spin = bindRaw(card.status.spinner, itemCtx) === true;
115
- const frame = spin ? `${spinnerFrame(Math.max(0, tick - 1), !ctx.unicode)} ` : "";
116
- const statusMarker = `${toneMarker(statusTone, !ctx.unicode)} `;
117
- lines.push(pad + clamp(`${frame}${statusMarker}${toneFg(ctx.theme, statusTone)(label)}`));
118
- }
119
- }
120
- // 8. deps ("after: …").
121
- const depsRaw = bindRaw(card.deps, itemCtx);
122
- if (Array.isArray(depsRaw)) {
123
- const deps = depsRaw.filter((dep) => typeof dep === "string");
124
- if (deps.length > 0)
125
- lines.push(pad + clamp(`after: ${deps.join(", ")}`));
126
- }
127
- return lines;
128
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * The `collection` block (UI-blocks spec §5.5): the display dispatch.
3
- * Task 3 owns `list`/`table`/`grid`; Task 4 owns `lanes`/`calendar`.
4
- */
5
- import type { Component } from "@earendil-works/pi-tui";
6
- import type { CollectionBlock } from "@sigrun/shared/blocks";
7
- import type { BlockCtx } from "../context.js";
8
- /** The display dispatch. */
9
- export declare function renderCollection(block: CollectionBlock, ctx: BlockCtx): Component | undefined;
@@ -1,26 +0,0 @@
1
- /**
2
- * The `collection` block (UI-blocks spec §5.5): the display dispatch.
3
- * Task 3 owns `list`/`table`/`grid`; Task 4 owns `lanes`/`calendar`.
4
- */
5
- import { CalendarComponent } from "./calendar.js";
6
- import { GridComponent } from "./grid.js";
7
- import { LanesComponent } from "./lanes.js";
8
- import { ListComponent } from "./list.js";
9
- import { TableComponent } from "./table.js";
10
- /** The display dispatch. */
11
- export function renderCollection(block, ctx) {
12
- switch (block.display) {
13
- case "list":
14
- return new ListComponent(block, ctx);
15
- case "table":
16
- return new TableComponent(block, ctx);
17
- case "grid":
18
- return new GridComponent(block, ctx);
19
- case "lanes":
20
- return new LanesComponent(block, ctx);
21
- case "calendar":
22
- return new CalendarComponent(block, ctx);
23
- default:
24
- return undefined;
25
- }
26
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * The `grid` display (UI-blocks spec §5.5 + §12.3): cards in as many
3
- * columns as fit — `floor(width / token)` with the token from the item
4
- * template's `image` presence (`m` = 45 cells with an image, `s` = 30
5
- * without; §5.5's minItemWidth). `overflow: "scroll"` stays one
6
- * horizontal row (the row clips at the pane edge; there is no second
7
- * row). At compact width the fallback is one column (§12.3).
8
- */
9
- import type { Component } from "@earendil-works/pi-tui";
10
- import type { CollectionBlock } from "@sigrun/shared/blocks";
11
- import { type BlockCtx } from "../context.js";
12
- /** `grid`: floor(width / token) card columns. */
13
- export declare class GridComponent implements Component {
14
- #private;
15
- constructor(block: CollectionBlock, ctx: BlockCtx);
16
- render(width: number): string[];
17
- invalidate(): void;
18
- }
@@ -1,72 +0,0 @@
1
- /**
2
- * The `grid` display (UI-blocks spec §5.5 + §12.3): cards in as many
3
- * columns as fit — `floor(width / token)` with the token from the item
4
- * template's `image` presence (`m` = 45 cells with an image, `s` = 30
5
- * without; §5.5's minItemWidth). `overflow: "scroll"` stays one
6
- * horizontal row (the row clips at the pane edge; there is no second
7
- * row). At compact width the fallback is one column (§12.3).
8
- */
9
- import { pad, truncate, widthClassOf } from "../width.js";
10
- import { cardLines } from "./card.js";
11
- import { collectionItems, sortItems } from "./shared.js";
12
- /** `grid`: floor(width / token) card columns. */
13
- export class GridComponent {
14
- #block;
15
- #ctx;
16
- #tick = 0;
17
- constructor(block, ctx) {
18
- this.#block = block;
19
- this.#ctx = ctx;
20
- }
21
- #rows() {
22
- return sortItems(collectionItems(this.#block, this.#ctx), this.#block.sort);
23
- }
24
- render(width) {
25
- this.#tick += 1;
26
- const rows = this.#rows();
27
- const itemWidth = this.#block.item.image === undefined ? 30 : 45; // s / m token (§5.2)
28
- let columns = Math.max(1, Math.floor(width / itemWidth));
29
- if (this.#block.overflow === "scroll") {
30
- // One row, clipped at the pane edge.
31
- columns = Math.max(1, columns);
32
- return this.#renderRow(rows, width, itemWidth).slice(0, this.#rowHeight(rows, itemWidth, width));
33
- }
34
- if (widthClassOf(width) === "compact")
35
- columns = 1;
36
- const lines = [];
37
- for (let start = 0; start < rows.length; start += columns) {
38
- lines.push(...this.#renderRow(rows.slice(start, start + columns), width, itemWidth));
39
- }
40
- return lines;
41
- }
42
- /** The tallest card of a row, in lines. */
43
- #rowHeight(rows, itemWidth, width) {
44
- let height = 0;
45
- for (const row of rows) {
46
- height = Math.max(height, cardLines(this.#block.item, row.item, this.#ctx, itemWidth - 2, 0, this.#tick).length);
47
- }
48
- void width;
49
- return height;
50
- }
51
- /** One grid row: the cards side by side, each padded to `itemWidth`. */
52
- #renderRow(rows, width, itemWidth) {
53
- const perCard = rows.map((row) => cardLines(this.#block.item, row.item, this.#ctx, itemWidth - 2, 0, this.#tick));
54
- const height = perCard.reduce((max, card) => Math.max(max, card.length), 0);
55
- const lines = [];
56
- for (let line = 0; line < height; line++) {
57
- const cells = perCard.map((card) => pad(card[line] ?? "", itemWidth - 2));
58
- const joined = cells.join(" ");
59
- const text = joined.trimEnd() === "" ? "" : joined;
60
- if (text === "") {
61
- lines.push("");
62
- }
63
- else {
64
- lines.push(truncate(text.replace(/\s+$/, ""), width));
65
- }
66
- }
67
- return lines;
68
- }
69
- invalidate() {
70
- // The grid holds no view state beyond the spinner tick.
71
- }
72
- }
@@ -1,32 +0,0 @@
1
- /**
2
- * The `lanes` display (UI-blocks U2 Task 4, spec §5.5 + §12.3 + §12.4):
3
- * one lane per group, side by side in regular/wide; compact shows ONE
4
- * lane with a Tab/Shift+Tab switcher and a per-item "Move to…" menu
5
- * (the `.` action-menu) for cross-lane moves.
6
- *
7
- * Keyboard moves (§12.4): `m` picks the item under the cursor up,
8
- * arrows place it (left/right across lanes, up/down within), Enter
9
- * drops with the U1 pointer-drop payload `{itemId, from: {group,
10
- * index}, to: {group, index}}`, Esc cancels. Review Focus 1: the
11
- * pick-up survives a resize — the placement is recomputed from the
12
- * live block data on every key, never cached from the rendered lines.
13
- *
14
- * `x` checks, `Space` selects, Enter on an item with no pick-up in
15
- * flight activates it. Quiet items fold behind a per-lane "N more"
16
- * row. The cursor is (lane, row); at regular/wide lanes render side by
17
- * side and the cursor lane is the compact-selected one.
18
- */
19
- import type { Component } from "@earendil-works/pi-tui";
20
- import type { CollectionBlock } from "@sigrun/shared/blocks";
21
- import type { BlockCtx } from "../context.js";
22
- import { isQuiet } from "./shared.js";
23
- /** The lanes display. */
24
- export declare class LanesComponent implements Component {
25
- #private;
26
- constructor(block: CollectionBlock, ctx: BlockCtx);
27
- render(width: number): string[];
28
- handleInput(data: string): void;
29
- invalidate(): void;
30
- }
31
- /** Re-exported so tests can assert the compact switcher boundary. */
32
- export { isQuiet };