@profullstack/hqtui 0.1.0

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 (98) hide show
  1. package/README.md +144 -0
  2. package/dist/ansi.d.ts +42 -0
  3. package/dist/ansi.d.ts.map +1 -0
  4. package/dist/ansi.js +66 -0
  5. package/dist/ansi.js.map +1 -0
  6. package/dist/app.d.ts +128 -0
  7. package/dist/app.d.ts.map +1 -0
  8. package/dist/app.js +296 -0
  9. package/dist/app.js.map +1 -0
  10. package/dist/buffer.d.ts +50 -0
  11. package/dist/buffer.d.ts.map +1 -0
  12. package/dist/buffer.js +184 -0
  13. package/dist/buffer.js.map +1 -0
  14. package/dist/capabilities.d.ts +36 -0
  15. package/dist/capabilities.d.ts.map +1 -0
  16. package/dist/capabilities.js +93 -0
  17. package/dist/capabilities.js.map +1 -0
  18. package/dist/color.d.ts +47 -0
  19. package/dist/color.d.ts.map +1 -0
  20. package/dist/color.js +176 -0
  21. package/dist/color.js.map +1 -0
  22. package/dist/diff.d.ts +39 -0
  23. package/dist/diff.d.ts.map +1 -0
  24. package/dist/diff.js +203 -0
  25. package/dist/diff.js.map +1 -0
  26. package/dist/graphics/blocks.d.ts +26 -0
  27. package/dist/graphics/blocks.d.ts.map +1 -0
  28. package/dist/graphics/blocks.js +49 -0
  29. package/dist/graphics/blocks.js.map +1 -0
  30. package/dist/graphics/braille.d.ts +33 -0
  31. package/dist/graphics/braille.d.ts.map +1 -0
  32. package/dist/graphics/braille.js +166 -0
  33. package/dist/graphics/braille.js.map +1 -0
  34. package/dist/graphics/index.d.ts +4 -0
  35. package/dist/graphics/index.d.ts.map +1 -0
  36. package/dist/graphics/index.js +4 -0
  37. package/dist/graphics/index.js.map +1 -0
  38. package/dist/graphics/plot.d.ts +91 -0
  39. package/dist/graphics/plot.d.ts.map +1 -0
  40. package/dist/graphics/plot.js +291 -0
  41. package/dist/graphics/plot.js.map +1 -0
  42. package/dist/index.d.ts +34 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +42 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/input.d.ts +62 -0
  47. package/dist/input.d.ts.map +1 -0
  48. package/dist/input.js +245 -0
  49. package/dist/input.js.map +1 -0
  50. package/dist/layout.d.ts +51 -0
  51. package/dist/layout.d.ts.map +1 -0
  52. package/dist/layout.js +163 -0
  53. package/dist/layout.js.map +1 -0
  54. package/dist/surface.d.ts +81 -0
  55. package/dist/surface.d.ts.map +1 -0
  56. package/dist/surface.js +181 -0
  57. package/dist/surface.js.map +1 -0
  58. package/dist/terminal.d.ts +63 -0
  59. package/dist/terminal.d.ts.map +1 -0
  60. package/dist/terminal.js +189 -0
  61. package/dist/terminal.js.map +1 -0
  62. package/dist/testing.d.ts +71 -0
  63. package/dist/testing.d.ts.map +1 -0
  64. package/dist/testing.js +171 -0
  65. package/dist/testing.js.map +1 -0
  66. package/dist/theme.d.ts +58 -0
  67. package/dist/theme.d.ts.map +1 -0
  68. package/dist/theme.js +238 -0
  69. package/dist/theme.js.map +1 -0
  70. package/dist/ui.d.ts +194 -0
  71. package/dist/ui.d.ts.map +1 -0
  72. package/dist/ui.js +439 -0
  73. package/dist/ui.js.map +1 -0
  74. package/dist/unicode.d.ts +36 -0
  75. package/dist/unicode.d.ts.map +1 -0
  76. package/dist/unicode.js +221 -0
  77. package/dist/unicode.js.map +1 -0
  78. package/dist/widgets/controls.d.ts +95 -0
  79. package/dist/widgets/controls.d.ts.map +1 -0
  80. package/dist/widgets/controls.js +231 -0
  81. package/dist/widgets/controls.js.map +1 -0
  82. package/dist/widgets/index.d.ts +5 -0
  83. package/dist/widgets/index.d.ts.map +1 -0
  84. package/dist/widgets/index.js +5 -0
  85. package/dist/widgets/index.js.map +1 -0
  86. package/dist/widgets/meters.d.ts +100 -0
  87. package/dist/widgets/meters.d.ts.map +1 -0
  88. package/dist/widgets/meters.js +209 -0
  89. package/dist/widgets/meters.js.map +1 -0
  90. package/dist/widgets/table.d.ts +92 -0
  91. package/dist/widgets/table.d.ts.map +1 -0
  92. package/dist/widgets/table.js +228 -0
  93. package/dist/widgets/table.js.map +1 -0
  94. package/dist/widgets/text.d.ts +64 -0
  95. package/dist/widgets/text.d.ts.map +1 -0
  96. package/dist/widgets/text.js +123 -0
  97. package/dist/widgets/text.js.map +1 -0
  98. package/package.json +56 -0
package/dist/ui.d.ts ADDED
@@ -0,0 +1,194 @@
1
+ import type { Surface, BorderStyle, Align } from "./surface.ts";
2
+ import type { Color } from "./color.ts";
3
+ import type { Theme } from "./theme.ts";
4
+ import type { Capabilities } from "./capabilities.ts";
5
+ import { type Rect, type Padding, type Size } from "./layout.ts";
6
+ import { BrailleCanvas } from "./graphics/braille.ts";
7
+ import * as W from "./widgets/index.ts";
8
+ export interface HitRegion {
9
+ rect: Rect;
10
+ onClick?: (x: number, y: number, button: string) => void;
11
+ onScroll?: (delta: number) => void;
12
+ onHover?: (x: number, y: number) => void;
13
+ }
14
+ export interface FocusRegistration {
15
+ index: number;
16
+ focused: boolean;
17
+ }
18
+ /** Per-frame services the builder needs from the app. */
19
+ export interface RenderContext {
20
+ theme: Theme;
21
+ capabilities: Capabilities;
22
+ width: number;
23
+ height: number;
24
+ /** Frames drawn since start. */
25
+ frame: number;
26
+ /** Milliseconds since the app started. */
27
+ elapsed: number;
28
+ focusIndex: number;
29
+ registerFocus(action?: () => void): FocusRegistration;
30
+ hit(region: HitRegion): void;
31
+ overlay(draw: (root: Surface) => void): void;
32
+ invalidate(): void;
33
+ }
34
+ export interface ContainerOptions {
35
+ gap?: number;
36
+ padding?: Padding;
37
+ /** Size along the parent's main axis. */
38
+ size?: Size;
39
+ width?: Size;
40
+ height?: Size;
41
+ min?: number;
42
+ max?: number;
43
+ background?: Color;
44
+ }
45
+ export interface PanelOptions extends ContainerOptions {
46
+ title?: string;
47
+ titleAlign?: Align;
48
+ titleColor?: Color;
49
+ subtitle?: string;
50
+ subtitleColor?: Color;
51
+ footer?: string;
52
+ border?: BorderStyle;
53
+ borderColor?: Color;
54
+ /** Draws the focused border color and joins the Tab order. */
55
+ focusable?: boolean;
56
+ focused?: boolean;
57
+ scroll?: number;
58
+ }
59
+ export interface GridOptions extends ContainerOptions {
60
+ columns?: Size[] | number;
61
+ rows?: Size[] | number;
62
+ }
63
+ export interface CellOptions {
64
+ colSpan?: number;
65
+ rowSpan?: number;
66
+ }
67
+ /**
68
+ * The builder. Every container collects its children first, then solves the
69
+ * layout once and draws — which is why `"1fr"` works without a retained tree.
70
+ */
71
+ export declare class Container {
72
+ readonly surface: Surface;
73
+ readonly ctx: RenderContext;
74
+ readonly direction: "row" | "column";
75
+ private children;
76
+ private gap;
77
+ private inner;
78
+ constructor(surface: Surface, ctx: RenderContext, direction?: "row" | "column", options?: ContainerOptions);
79
+ get theme(): Theme;
80
+ get width(): number;
81
+ get height(): number;
82
+ /** Width available to a child laid out along the main axis. */
83
+ private get crossWidth();
84
+ private add;
85
+ private sizeOf;
86
+ /** Solve and draw. Called automatically for you by the app and by parents. */
87
+ flush(): void;
88
+ /** A horizontal container. Children default to equal shares. */
89
+ row(options?: ContainerOptions, build?: (row: Container) => void): this;
90
+ /** A vertical container. */
91
+ column(options?: ContainerOptions, build?: (column: Container) => void): this;
92
+ /**
93
+ * A CSS-ish grid. Children are placed in order and may span cells:
94
+ *
95
+ * ui.grid({ columns: ["2fr", "1fr"], rows: [12, "1fr"] }, g => {
96
+ * g.panel({ title: "CPU" });
97
+ * g.panel({ title: "Memory", colSpan: 2 });
98
+ * });
99
+ */
100
+ grid(options?: GridOptions, build?: (grid: GridContainer) => void): this;
101
+ /** A bordered panel. The callback receives its interior as a column. */
102
+ panel(options?: PanelOptions, build?: (panel: Container) => void): this;
103
+ /** A panel without a border — a grouping box that costs no rows. */
104
+ box(options?: PanelOptions, build?: (box: Container) => void): this;
105
+ /** Blank space. */
106
+ spacer(size?: Size): this;
107
+ /** A horizontal rule, optionally labelled. */
108
+ divider(options?: W.DividerOptions & ContainerOptions): this;
109
+ text(content: string, options?: W.TextOptions & ContainerOptions): this;
110
+ /** Muted secondary text. */
111
+ label(content: string, options?: W.TextOptions & ContainerOptions): this;
112
+ /** Bold heading in the theme's title color. */
113
+ heading(content: string, options?: W.TextOptions & ContainerOptions): this;
114
+ badge(options: W.BadgeOptions & ContainerOptions): this;
115
+ /** Aligned label/value pairs. */
116
+ keyValues(rows: W.KeyValueRow[], options?: Omit<W.KeyValueOptions, "rows"> & ContainerOptions): this;
117
+ table<Row>(options: W.TableOptions<Row> & ContainerOptions): this;
118
+ list(options: W.ListOptions & ContainerOptions): this;
119
+ tree(options: W.TreeOptions & ContainerOptions): this;
120
+ log(options: W.LogOptions & ContainerOptions): this;
121
+ /** `label ████████░░░ 42%` */
122
+ meter(options: W.MeterOptions & ContainerOptions): this;
123
+ /** A stack or grid of meters. */
124
+ meters(items: W.MetersOptions["items"], options?: Omit<W.MetersOptions, "items"> & ContainerOptions): this;
125
+ progress(options: W.ProgressOptions & ContainerOptions): this;
126
+ /** Braille line/area graph. Fills the space it is given. */
127
+ graph(options: W.GraphOptions & ContainerOptions): this;
128
+ /** A filled area graph — `graph` with `fill` on. */
129
+ areaGraph(options: W.GraphOptions & ContainerOptions): this;
130
+ /** Several series on one set of axes. */
131
+ multiGraph(series: W.GraphOptions["series"], options?: Omit<W.GraphOptions, "series"> & ContainerOptions): this;
132
+ sparkline(options: W.SparklineOptions & ContainerOptions): this;
133
+ histogram(options: W.ColumnsOptions & ContainerOptions): this;
134
+ /** A semicircular dial. Wants at least 9x5. */
135
+ gauge(options: W.GaugeOptions & ContainerOptions): this;
136
+ donut(options: W.DonutOptions & ContainerOptions): this;
137
+ /** Segmented temperature-style bar. */
138
+ heatBar(options: W.HeatBarOptions & ContainerOptions): this;
139
+ /** A button. Pass `onPress` and it joins the Tab order automatically. */
140
+ button(options: W.ButtonOptions & ContainerOptions & {
141
+ onPress?: () => void;
142
+ }): this;
143
+ /** A row of buttons, sized to their labels. */
144
+ buttons(items: (W.ButtonOptions & {
145
+ onPress?: () => void;
146
+ })[], options?: ContainerOptions & {
147
+ align?: Align;
148
+ }): this;
149
+ checkbox(options: W.CheckboxOptions & ContainerOptions & {
150
+ onToggle?: () => void;
151
+ }): this;
152
+ select(options: W.SelectOptions & ContainerOptions & {
153
+ onOpen?: () => void;
154
+ }): this;
155
+ textInput(options: W.TextInputOptions & ContainerOptions): this;
156
+ tabs(options: W.TabsOptions & ContainerOptions & {
157
+ onSelect?: (index: number) => void;
158
+ }): this;
159
+ statusBar(options: W.StatusBarOptions & ContainerOptions): this;
160
+ /** A centred dialog drawn above everything else this frame. */
161
+ modal(options: W.ModalOptions, build?: (modal: Container) => void): this;
162
+ commandPalette(options: W.CommandPaletteOptions): this;
163
+ tooltip(options: W.TooltipOptions): this;
164
+ /** Draw straight onto the framebuffer region. Nothing is off limits. */
165
+ draw(fn: (surface: Surface) => void, options?: ContainerOptions): this;
166
+ /** A Braille pixel canvas sized to the region, blitted when you are done. */
167
+ canvas(fn: (canvas: BrailleCanvas, surface: Surface) => void, options?: ContainerOptions & {
168
+ color?: Color;
169
+ }): this;
170
+ /**
171
+ * Pick a layout by available width:
172
+ * ui.responsive({ 120: wide, 80: medium, 0: compact });
173
+ */
174
+ responsive(breakpoints: Record<number, (ui: Container) => void>): this;
175
+ /** Run `build` only when the container is at least this wide. */
176
+ when(condition: boolean, build: (ui: Container) => void): this;
177
+ }
178
+ /** Grid placement with spans. Cells are filled row-major. */
179
+ export declare class GridContainer {
180
+ private surface;
181
+ private ctx;
182
+ private options;
183
+ private cells;
184
+ constructor(surface: Surface, ctx: RenderContext, options: GridOptions);
185
+ get theme(): Theme;
186
+ private track;
187
+ private push;
188
+ /** A panel occupying the next free cell (or several, with colSpan/rowSpan). */
189
+ panel(options?: PanelOptions & CellOptions, build?: (panel: Container) => void): this;
190
+ cell(options?: CellOptions & ContainerOptions, build?: (cell: Container) => void): this;
191
+ row(options?: CellOptions & ContainerOptions, build?: (row: Container) => void): this;
192
+ flush(): void;
193
+ }
194
+ //# sourceMappingURL=ui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"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"}
package/dist/ui.js ADDED
@@ -0,0 +1,439 @@
1
+ import { inset, stack, solve, isEmpty, } from "./layout.js";
2
+ import { stringWidth, wrap } from "./unicode.js";
3
+ import { BrailleCanvas } from "./graphics/braille.js";
4
+ import * as W from "./widgets/index.js";
5
+ /**
6
+ * The builder. Every container collects its children first, then solves the
7
+ * layout once and draws — which is why `"1fr"` works without a retained tree.
8
+ */
9
+ export class Container {
10
+ surface;
11
+ ctx;
12
+ direction;
13
+ children = [];
14
+ gap;
15
+ inner;
16
+ constructor(surface, ctx, direction = "column", options = {}) {
17
+ this.surface = surface;
18
+ this.ctx = ctx;
19
+ this.direction = direction;
20
+ this.gap = options.gap ?? 0;
21
+ this.inner = options.padding ? surface.inset(options.padding) : surface;
22
+ if (options.background !== undefined)
23
+ surface.fill({ bg: options.background });
24
+ }
25
+ get theme() {
26
+ return this.surface.theme;
27
+ }
28
+ get width() {
29
+ return this.inner.width;
30
+ }
31
+ get height() {
32
+ return this.inner.height;
33
+ }
34
+ /** Width available to a child laid out along the main axis. */
35
+ get crossWidth() {
36
+ return this.direction === "column" ? this.inner.width : this.inner.height;
37
+ }
38
+ add(draw, constraint = {}) {
39
+ this.children.push({ draw, constraint });
40
+ return this;
41
+ }
42
+ sizeOf(o, fallback, intrinsic) {
43
+ const explicit = o?.size ?? (this.direction === "column" ? o?.height : o?.width);
44
+ if (explicit !== undefined)
45
+ return { size: explicit, min: o?.min, max: o?.max, intrinsic };
46
+ // Intrinsic sizes describe height. Along a row, a widget takes the space it is given.
47
+ const size = this.direction === "row" ? "fill" : fallback;
48
+ return { size, min: o?.min, max: o?.max, intrinsic };
49
+ }
50
+ /** Solve and draw. Called automatically for you by the app and by parents. */
51
+ flush() {
52
+ if (this.children.length === 0 || isEmpty(this.inner.rect))
53
+ return;
54
+ const rects = stack(this.inner.rect, this.children.map((c) => c.constraint), this.direction, this.gap);
55
+ this.children.forEach((child, i) => {
56
+ const rect = rects[i];
57
+ if (!rect || isEmpty(rect))
58
+ return;
59
+ child.draw(this.inner.region(rect));
60
+ });
61
+ this.children.length = 0;
62
+ }
63
+ // ---------------------------------------------------------------- layout
64
+ /** A horizontal container. Children default to equal shares. */
65
+ row(options = {}, build) {
66
+ return this.add((surface) => {
67
+ const container = new Container(surface, this.ctx, "row", options);
68
+ build?.(container);
69
+ container.flush();
70
+ }, this.sizeOf(options, "fill"));
71
+ }
72
+ /** A vertical container. */
73
+ column(options = {}, build) {
74
+ return this.add((surface) => {
75
+ const container = new Container(surface, this.ctx, "column", options);
76
+ build?.(container);
77
+ container.flush();
78
+ }, this.sizeOf(options, "fill"));
79
+ }
80
+ /**
81
+ * A CSS-ish grid. Children are placed in order and may span cells:
82
+ *
83
+ * ui.grid({ columns: ["2fr", "1fr"], rows: [12, "1fr"] }, g => {
84
+ * g.panel({ title: "CPU" });
85
+ * g.panel({ title: "Memory", colSpan: 2 });
86
+ * });
87
+ */
88
+ grid(options = {}, build) {
89
+ return this.add((surface) => {
90
+ const container = new GridContainer(surface, this.ctx, options);
91
+ build?.(container);
92
+ container.flush();
93
+ }, this.sizeOf(options, "fill"));
94
+ }
95
+ /** A bordered panel. The callback receives its interior as a column. */
96
+ panel(options = {}, build) {
97
+ const focus = options.focusable ? this.ctx.registerFocus() : undefined;
98
+ return this.add((surface) => {
99
+ const focused = options.focused ?? focus?.focused ?? false;
100
+ const boxOptions = {
101
+ title: options.title,
102
+ titleAlign: options.titleAlign,
103
+ titleColor: options.titleColor,
104
+ subtitle: options.subtitle,
105
+ subtitleColor: options.subtitleColor,
106
+ footer: options.footer,
107
+ border: options.border ?? "rounded",
108
+ borderColor: options.borderColor ?? (focused ? this.theme.borderFocused : this.theme.border),
109
+ bg: options.background,
110
+ };
111
+ const interior = surface.box(boxOptions);
112
+ const container = new Container(interior, this.ctx, "column", {
113
+ gap: options.gap,
114
+ padding: options.padding ?? [0, 1],
115
+ });
116
+ build?.(container);
117
+ container.flush();
118
+ }, this.sizeOf(options, "fill"));
119
+ }
120
+ /** A panel without a border — a grouping box that costs no rows. */
121
+ box(options = {}, build) {
122
+ return this.panel({ ...options, border: options.border ?? "none" }, build);
123
+ }
124
+ /** Blank space. */
125
+ spacer(size = "fill") {
126
+ return this.add(() => { }, { size });
127
+ }
128
+ /** A horizontal rule, optionally labelled. */
129
+ divider(options = {}) {
130
+ return this.add((s) => W.drawDivider(s, options), this.sizeOf(options, "auto", 1));
131
+ }
132
+ // ------------------------------------------------------------------ text
133
+ text(content, options = {}) {
134
+ const lines = options.wrap ? wrap(content, this.crossWidth).length : content.split("\n").length;
135
+ return this.add((s) => W.drawText(s, content, options), this.sizeOf(options, "auto", lines));
136
+ }
137
+ /** Muted secondary text. */
138
+ label(content, options = {}) {
139
+ return this.text(content, { fg: this.theme.muted, ...options });
140
+ }
141
+ /** Bold heading in the theme's title color. */
142
+ heading(content, options = {}) {
143
+ return this.text(content, { fg: this.theme.title, bold: true, ...options });
144
+ }
145
+ badge(options) {
146
+ return this.add((s) => W.drawBadge(s, options), this.sizeOf(options, "auto", 1));
147
+ }
148
+ /** Aligned label/value pairs. */
149
+ keyValues(rows, options = {}) {
150
+ return this.add((s) => W.drawKeyValues(s, { ...options, rows }), this.sizeOf(options, "auto", rows.length));
151
+ }
152
+ // ------------------------------------------------------------------ data
153
+ table(options) {
154
+ const intrinsic = options.rows.length + (options.header === false ? 0 : 1);
155
+ return this.add((s) => W.drawTable(s, options), this.sizeOf(options, "fill", intrinsic));
156
+ }
157
+ list(options) {
158
+ return this.add((s) => W.drawList(s, options), this.sizeOf(options, "fill", options.items.length));
159
+ }
160
+ tree(options) {
161
+ return this.add((s) => W.drawTree(s, options), this.sizeOf(options, "fill"));
162
+ }
163
+ log(options) {
164
+ return this.add((s) => W.drawLog(s, options), this.sizeOf(options, "fill", options.entries.length));
165
+ }
166
+ // --------------------------------------------------------------- metrics
167
+ /** `label ████████░░░ 42%` */
168
+ meter(options) {
169
+ return this.add((s) => W.drawMeter(s, options), this.sizeOf(options, "auto", 1));
170
+ }
171
+ /** A stack or grid of meters. */
172
+ meters(items, options = {}) {
173
+ const columns = Math.max(1, options.columns ?? 1);
174
+ const rows = Math.ceil(items.length / columns);
175
+ return this.add((s) => W.drawMeters(s, { ...options, items }), this.sizeOf(options, "auto", rows));
176
+ }
177
+ progress(options) {
178
+ return this.add((s) => W.drawProgress(s, options), this.sizeOf(options, "auto", 1));
179
+ }
180
+ /** Braille line/area graph. Fills the space it is given. */
181
+ graph(options) {
182
+ return this.add((s) => W.drawGraph(s, options), this.sizeOf(options, "fill"));
183
+ }
184
+ /** A filled area graph — `graph` with `fill` on. */
185
+ areaGraph(options) {
186
+ return this.graph({ fill: true, ...options });
187
+ }
188
+ /** Several series on one set of axes. */
189
+ multiGraph(series, options = {}) {
190
+ return this.graph({ ...options, series });
191
+ }
192
+ sparkline(options) {
193
+ return this.add((s) => W.drawSparklineWidget(s, options), this.sizeOf(options, "auto", 1));
194
+ }
195
+ histogram(options) {
196
+ return this.add((s) => W.drawColumns(s, options), this.sizeOf(options, "fill"));
197
+ }
198
+ /** A semicircular dial. Wants at least 9x5. */
199
+ gauge(options) {
200
+ return this.add((s) => W.drawGauge(s, options), this.sizeOf(options, "fill"));
201
+ }
202
+ donut(options) {
203
+ return this.add((s) => W.drawDonut(s, options), this.sizeOf(options, "fill"));
204
+ }
205
+ /** Segmented temperature-style bar. */
206
+ heatBar(options) {
207
+ return this.add((s) => W.drawHeatBar(s, options), this.sizeOf(options, "auto", 1));
208
+ }
209
+ // ---------------------------------------------------------------- inputs
210
+ /** A button. Pass `onPress` and it joins the Tab order automatically. */
211
+ button(options) {
212
+ const focus = this.ctx.registerFocus(options.onPress);
213
+ return this.add((s) => {
214
+ W.drawButton(s, { ...options, focused: options.focused ?? focus.focused });
215
+ this.ctx.hit({ rect: s.hitRect(), onClick: () => options.onPress?.() });
216
+ }, this.sizeOf(options, "auto", 1));
217
+ }
218
+ /** A row of buttons, sized to their labels. */
219
+ buttons(items, options = {}) {
220
+ return this.row({ ...options, size: options.size ?? 1 }, (row) => {
221
+ if (options.align === "right")
222
+ row.spacer("fill");
223
+ items.forEach((item) => {
224
+ row.button({ ...item, width: stringWidth(item.label) + 2, size: stringWidth(item.label) + 2 });
225
+ row.spacer(2);
226
+ });
227
+ if (options.align !== "right")
228
+ row.spacer("fill");
229
+ });
230
+ }
231
+ checkbox(options) {
232
+ const focus = this.ctx.registerFocus(options.onToggle);
233
+ return this.add((s) => {
234
+ W.drawCheckbox(s, { ...options, focused: options.focused ?? focus.focused });
235
+ this.ctx.hit({ rect: s.hitRect(), onClick: () => options.onToggle?.() });
236
+ }, this.sizeOf(options, "auto", 1));
237
+ }
238
+ select(options) {
239
+ const focus = this.ctx.registerFocus(options.onOpen);
240
+ return this.add((s) => {
241
+ W.drawSelect(s, { ...options, focused: options.focused ?? focus.focused });
242
+ this.ctx.hit({ rect: s.hitRect(), onClick: () => options.onOpen?.() });
243
+ }, this.sizeOf(options, "auto", options.open ? (options.options?.length ?? 0) + 1 : 1));
244
+ }
245
+ textInput(options) {
246
+ const focus = this.ctx.registerFocus();
247
+ return this.add((s) => W.drawTextInput(s, { ...options, focused: options.focused ?? focus.focused }), this.sizeOf(options, "auto", 1));
248
+ }
249
+ tabs(options) {
250
+ return this.add((s) => {
251
+ W.drawTabs(s, options);
252
+ if (options.onSelect) {
253
+ let x = 0;
254
+ options.tabs.forEach((tab, i) => {
255
+ const w = stringWidth(tab) + 4;
256
+ const r = s.hitRect();
257
+ this.ctx.hit({
258
+ rect: { x: r.x + x, y: r.y, width: w, height: 1 },
259
+ onClick: () => options.onSelect?.(i),
260
+ });
261
+ x += w;
262
+ });
263
+ }
264
+ }, this.sizeOf(options, "auto", 1));
265
+ }
266
+ statusBar(options) {
267
+ return this.add((s) => W.drawStatusBar(s, options), this.sizeOf(options, "auto", 1));
268
+ }
269
+ // -------------------------------------------------------------- overlays
270
+ /** A centred dialog drawn above everything else this frame. */
271
+ modal(options, build) {
272
+ this.ctx.overlay((root) => {
273
+ const inner = W.drawModal(root, options);
274
+ if (build) {
275
+ const container = new Container(inner, this.ctx, "column", { padding: [1, 1] });
276
+ build(container);
277
+ container.flush();
278
+ }
279
+ });
280
+ return this;
281
+ }
282
+ commandPalette(options) {
283
+ this.ctx.overlay((root) => W.drawCommandPalette(root, options));
284
+ return this;
285
+ }
286
+ tooltip(options) {
287
+ this.ctx.overlay((root) => W.drawTooltip(root, options));
288
+ return this;
289
+ }
290
+ // --------------------------------------------------------- escape hatches
291
+ /** Draw straight onto the framebuffer region. Nothing is off limits. */
292
+ draw(fn, options = {}) {
293
+ return this.add(fn, this.sizeOf(options, "fill"));
294
+ }
295
+ /** A Braille pixel canvas sized to the region, blitted when you are done. */
296
+ canvas(fn, options = {}) {
297
+ return this.add((surface) => {
298
+ const canvas = new BrailleCanvas(surface.width, surface.height);
299
+ fn(canvas, surface);
300
+ const color = options.color ?? this.theme.accent;
301
+ for (let row = 0; row < canvas.rows; row++) {
302
+ for (let col = 0; col < canvas.cols; col++) {
303
+ const value = canvas.cell(col, row);
304
+ if (value !== 0)
305
+ surface.char(col, row, value, { fg: color });
306
+ }
307
+ }
308
+ }, this.sizeOf(options, "fill"));
309
+ }
310
+ /**
311
+ * Pick a layout by available width:
312
+ * ui.responsive({ 120: wide, 80: medium, 0: compact });
313
+ */
314
+ responsive(breakpoints) {
315
+ const widths = Object.keys(breakpoints)
316
+ .map(Number)
317
+ .sort((a, b) => b - a);
318
+ const chosen = widths.find((w) => this.width >= w) ?? widths[widths.length - 1];
319
+ breakpoints[chosen]?.(this);
320
+ return this;
321
+ }
322
+ /** Run `build` only when the container is at least this wide. */
323
+ when(condition, build) {
324
+ if (condition)
325
+ build(this);
326
+ return this;
327
+ }
328
+ }
329
+ /** Grid placement with spans. Cells are filled row-major. */
330
+ export class GridContainer {
331
+ surface;
332
+ ctx;
333
+ options;
334
+ cells = [];
335
+ constructor(surface, ctx, options) {
336
+ this.surface = options.padding ? surface.inset(options.padding) : surface;
337
+ this.ctx = ctx;
338
+ this.options = options;
339
+ }
340
+ get theme() {
341
+ return this.surface.theme;
342
+ }
343
+ track(spec, fallback) {
344
+ if (Array.isArray(spec))
345
+ return spec;
346
+ const count = spec ?? fallback;
347
+ return new Array(Math.max(1, count)).fill("1fr");
348
+ }
349
+ push(options, draw) {
350
+ this.cells.push({ options, draw });
351
+ return this;
352
+ }
353
+ /** A panel occupying the next free cell (or several, with colSpan/rowSpan). */
354
+ panel(options = {}, build) {
355
+ return this.push(options, (surface) => {
356
+ const container = new Container(surface, this.ctx, "column");
357
+ container.panel(options, build);
358
+ container.flush();
359
+ });
360
+ }
361
+ cell(options = {}, build) {
362
+ return this.push(options, (surface) => {
363
+ const container = new Container(surface, this.ctx, "column", options);
364
+ build?.(container);
365
+ container.flush();
366
+ });
367
+ }
368
+ row(options = {}, build) {
369
+ return this.push(options, (surface) => {
370
+ const container = new Container(surface, this.ctx, "row", options);
371
+ build?.(container);
372
+ container.flush();
373
+ });
374
+ }
375
+ flush() {
376
+ if (this.cells.length === 0 || isEmpty(this.surface.rect))
377
+ return;
378
+ const gap = this.options.gap ?? 0;
379
+ const columnSpec = this.track(this.options.columns, Math.min(this.cells.length, 3));
380
+ const rowCount = Array.isArray(this.options.rows)
381
+ ? this.options.rows.length
382
+ : (this.options.rows ?? Math.ceil(this.cells.length / columnSpec.length));
383
+ const rowSpec = this.track(this.options.rows, rowCount);
384
+ const colWidths = solve(this.surface.width, columnSpec.map((size) => ({ size })), gap);
385
+ const rowHeights = solve(this.surface.height, rowSpec.map((size) => ({ size })), gap);
386
+ const occupied = new Set();
387
+ let cursor = 0;
388
+ for (const cell of this.cells) {
389
+ const colSpan = Math.max(1, cell.options.colSpan ?? 1);
390
+ const rowSpan = Math.max(1, cell.options.rowSpan ?? 1);
391
+ // Find the next free slot that fits the span.
392
+ let placed = false;
393
+ while (!placed && cursor < colWidths.length * rowHeights.length + colWidths.length) {
394
+ const col = cursor % colWidths.length;
395
+ const row = Math.floor(cursor / colWidths.length);
396
+ if (row >= rowHeights.length)
397
+ break;
398
+ let free = col + colSpan <= colWidths.length;
399
+ if (free) {
400
+ for (let r = row; r < row + rowSpan && free; r++) {
401
+ for (let c = col; c < col + colSpan && free; c++) {
402
+ if (occupied.has(`${c},${r}`))
403
+ free = false;
404
+ }
405
+ }
406
+ }
407
+ if (!free) {
408
+ cursor++;
409
+ continue;
410
+ }
411
+ for (let r = row; r < row + rowSpan; r++) {
412
+ for (let c = col; c < col + colSpan; c++)
413
+ occupied.add(`${c},${r}`);
414
+ }
415
+ let x = this.surface.rect.x;
416
+ for (let c = 0; c < col; c++)
417
+ x += colWidths[c] + gap;
418
+ let y = this.surface.rect.y;
419
+ for (let r = 0; r < row; r++)
420
+ y += rowHeights[r] + gap;
421
+ let width = 0;
422
+ for (let c = col; c < col + colSpan && c < colWidths.length; c++)
423
+ width += colWidths[c] + gap;
424
+ width = Math.max(0, width - gap);
425
+ let height = 0;
426
+ for (let r = row; r < row + rowSpan && r < rowHeights.length; r++)
427
+ height += rowHeights[r] + gap;
428
+ height = Math.max(0, height - gap);
429
+ const rect = { x, y, width, height };
430
+ if (!isEmpty(rect))
431
+ cell.draw(this.surface.region(rect));
432
+ placed = true;
433
+ cursor++;
434
+ }
435
+ }
436
+ this.cells.length = 0;
437
+ }
438
+ }
439
+ //# sourceMappingURL=ui.js.map