@flyingrobots/bijou-tui 3.1.0 → 4.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 (158) hide show
  1. package/LICENSE +159 -21
  2. package/README.md +205 -39
  3. package/dist/app-frame-actions.d.ts +5 -5
  4. package/dist/app-frame-actions.d.ts.map +1 -1
  5. package/dist/app-frame-actions.js +74 -9
  6. package/dist/app-frame-actions.js.map +1 -1
  7. package/dist/app-frame-i18n.d.ts +12 -0
  8. package/dist/app-frame-i18n.d.ts.map +1 -0
  9. package/dist/app-frame-i18n.js +92 -0
  10. package/dist/app-frame-i18n.js.map +1 -0
  11. package/dist/app-frame-layers.d.ts +29 -0
  12. package/dist/app-frame-layers.d.ts.map +1 -0
  13. package/dist/app-frame-layers.js +104 -0
  14. package/dist/app-frame-layers.js.map +1 -0
  15. package/dist/app-frame-palette.d.ts +6 -2
  16. package/dist/app-frame-palette.d.ts.map +1 -1
  17. package/dist/app-frame-palette.js +42 -10
  18. package/dist/app-frame-palette.js.map +1 -1
  19. package/dist/app-frame-render.d.ts +28 -14
  20. package/dist/app-frame-render.d.ts.map +1 -1
  21. package/dist/app-frame-render.js +285 -138
  22. package/dist/app-frame-render.js.map +1 -1
  23. package/dist/app-frame-types.d.ts +41 -21
  24. package/dist/app-frame-types.d.ts.map +1 -1
  25. package/dist/app-frame-types.js +8 -6
  26. package/dist/app-frame-types.js.map +1 -1
  27. package/dist/app-frame-utils.d.ts +8 -3
  28. package/dist/app-frame-utils.d.ts.map +1 -1
  29. package/dist/app-frame-utils.js +42 -27
  30. package/dist/app-frame-utils.js.map +1 -1
  31. package/dist/app-frame.d.ts +128 -12
  32. package/dist/app-frame.d.ts.map +1 -1
  33. package/dist/app-frame.js +1212 -91
  34. package/dist/app-frame.js.map +1 -1
  35. package/dist/browsable-list.d.ts +20 -1
  36. package/dist/browsable-list.d.ts.map +1 -1
  37. package/dist/browsable-list.js +48 -10
  38. package/dist/browsable-list.js.map +1 -1
  39. package/dist/collection-surface.d.ts +8 -0
  40. package/dist/collection-surface.d.ts.map +1 -0
  41. package/dist/collection-surface.js +41 -0
  42. package/dist/collection-surface.js.map +1 -0
  43. package/dist/command-palette.d.ts +17 -1
  44. package/dist/command-palette.d.ts.map +1 -1
  45. package/dist/command-palette.js +50 -20
  46. package/dist/command-palette.js.map +1 -1
  47. package/dist/commands.js +1 -1
  48. package/dist/commands.js.map +1 -1
  49. package/dist/css/text-style.d.ts +2 -1
  50. package/dist/css/text-style.d.ts.map +1 -1
  51. package/dist/css/text-style.js +33 -0
  52. package/dist/css/text-style.js.map +1 -1
  53. package/dist/design-language.d.ts +49 -0
  54. package/dist/design-language.d.ts.map +1 -0
  55. package/dist/design-language.js +70 -0
  56. package/dist/design-language.js.map +1 -0
  57. package/dist/driver.d.ts +6 -1
  58. package/dist/driver.d.ts.map +1 -1
  59. package/dist/driver.js +3 -0
  60. package/dist/driver.js.map +1 -1
  61. package/dist/eventbus.d.ts.map +1 -1
  62. package/dist/eventbus.js +21 -1
  63. package/dist/eventbus.js.map +1 -1
  64. package/dist/file-picker.d.ts +19 -1
  65. package/dist/file-picker.d.ts.map +1 -1
  66. package/dist/file-picker.js +47 -20
  67. package/dist/file-picker.js.map +1 -1
  68. package/dist/flex.d.ts +35 -1
  69. package/dist/flex.d.ts.map +1 -1
  70. package/dist/flex.js +127 -1
  71. package/dist/flex.js.map +1 -1
  72. package/dist/focus-area.d.ts +20 -1
  73. package/dist/focus-area.d.ts.map +1 -1
  74. package/dist/focus-area.js +107 -12
  75. package/dist/focus-area.js.map +1 -1
  76. package/dist/grid.d.ts +10 -0
  77. package/dist/grid.d.ts.map +1 -1
  78. package/dist/grid.js +25 -0
  79. package/dist/grid.js.map +1 -1
  80. package/dist/help.d.ts +41 -0
  81. package/dist/help.d.ts.map +1 -1
  82. package/dist/help.js +50 -0
  83. package/dist/help.js.map +1 -1
  84. package/dist/index.d.ts +23 -17
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +22 -16
  87. package/dist/index.js.map +1 -1
  88. package/dist/inspector-drawer.d.ts +36 -0
  89. package/dist/inspector-drawer.d.ts.map +1 -0
  90. package/dist/inspector-drawer.js +68 -0
  91. package/dist/inspector-drawer.js.map +1 -0
  92. package/dist/layout-node-surface.d.ts +17 -0
  93. package/dist/layout-node-surface.d.ts.map +1 -0
  94. package/dist/layout-node-surface.js +61 -0
  95. package/dist/layout-node-surface.js.map +1 -0
  96. package/dist/navigable-table.d.ts +16 -1
  97. package/dist/navigable-table.d.ts.map +1 -1
  98. package/dist/navigable-table.js +32 -1
  99. package/dist/navigable-table.js.map +1 -1
  100. package/dist/notification.d.ts +26 -1
  101. package/dist/notification.d.ts.map +1 -1
  102. package/dist/notification.js +294 -41
  103. package/dist/notification.js.map +1 -1
  104. package/dist/overlay.d.ts +29 -8
  105. package/dist/overlay.d.ts.map +1 -1
  106. package/dist/overlay.js +248 -137
  107. package/dist/overlay.js.map +1 -1
  108. package/dist/pager.d.ts +16 -0
  109. package/dist/pager.d.ts.map +1 -1
  110. package/dist/pager.js +61 -1
  111. package/dist/pager.js.map +1 -1
  112. package/dist/runtime-engine.d.ts +223 -0
  113. package/dist/runtime-engine.d.ts.map +1 -0
  114. package/dist/runtime-engine.js +457 -0
  115. package/dist/runtime-engine.js.map +1 -0
  116. package/dist/runtime.d.ts.map +1 -1
  117. package/dist/runtime.js +33 -19
  118. package/dist/runtime.js.map +1 -1
  119. package/dist/shell-quit.d.ts +12 -0
  120. package/dist/shell-quit.d.ts.map +1 -0
  121. package/dist/shell-quit.js +36 -0
  122. package/dist/shell-quit.js.map +1 -0
  123. package/dist/split-pane.d.ts +12 -1
  124. package/dist/split-pane.d.ts.map +1 -1
  125. package/dist/split-pane.js +31 -1
  126. package/dist/split-pane.js.map +1 -1
  127. package/dist/status-bar.d.ts +12 -0
  128. package/dist/status-bar.d.ts.map +1 -1
  129. package/dist/status-bar.js +45 -16
  130. package/dist/status-bar.js.map +1 -1
  131. package/dist/subapp/mount.d.ts.map +1 -1
  132. package/dist/subapp/mount.js +3 -0
  133. package/dist/subapp/mount.js.map +1 -1
  134. package/dist/surface-layout.d.ts +19 -0
  135. package/dist/surface-layout.d.ts.map +1 -0
  136. package/dist/surface-layout.js +87 -0
  137. package/dist/surface-layout.js.map +1 -0
  138. package/dist/transition-shaders.d.ts +10 -8
  139. package/dist/transition-shaders.d.ts.map +1 -1
  140. package/dist/transition-shaders.js +65 -19
  141. package/dist/transition-shaders.js.map +1 -1
  142. package/dist/types.d.ts +21 -7
  143. package/dist/types.d.ts.map +1 -1
  144. package/dist/types.js +11 -0
  145. package/dist/types.js.map +1 -1
  146. package/dist/view-output.d.ts +5 -4
  147. package/dist/view-output.d.ts.map +1 -1
  148. package/dist/view-output.js +37 -29
  149. package/dist/view-output.js.map +1 -1
  150. package/dist/viewport.d.ts +30 -1
  151. package/dist/viewport.d.ts.map +1 -1
  152. package/dist/viewport.js +77 -1
  153. package/dist/viewport.js.map +1 -1
  154. package/package.json +6 -3
  155. package/dist/layout-v3.d.ts +0 -10
  156. package/dist/layout-v3.d.ts.map +0 -1
  157. package/dist/layout-v3.js +0 -35
  158. package/dist/layout-v3.js.map +0 -1
package/dist/overlay.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  * Overlay compositing primitives for TUI apps.
3
3
  *
4
4
  * - `composite()` — paint overlays onto a background string (ANSI-safe)
5
+ * - `compositeSurface()` — paint overlays onto a background surface
5
6
  * - `modal()` — centered dialog overlay with title, body, hint
6
7
  * - `toast()` — anchored notification overlay with variant icons
7
8
  * - `tooltip()` — positioned overlay relative to a target element
@@ -28,22 +29,26 @@ export interface CompositeOptions {
28
29
  /** Wrap background lines in dim (ANSI 2m). */
29
30
  readonly dim?: boolean;
30
31
  }
32
+ /** Structured overlay content accepted by the surface-native overlay family. */
33
+ export type OverlayContent = string | Surface;
31
34
  /**
32
35
  * Configuration for the {@link modal} overlay.
33
36
  */
34
37
  export interface ModalOptions {
35
38
  /** Optional title displayed at the top of the modal (bolded when ctx provided). */
36
39
  readonly title?: string;
37
- /** Body content of the modal. */
38
- readonly body: string;
39
- /** Optional hint text displayed below the body (muted when ctx provided). */
40
- readonly hint?: string;
40
+ /** Body content of the modal. Accepts plain text or a structured surface. */
41
+ readonly body: OverlayContent;
42
+ /** Optional hint content displayed below the body (muted when ctx provided). */
43
+ readonly hint?: OverlayContent;
41
44
  /** Screen width in columns, used for centering. */
42
45
  readonly screenWidth: number;
43
46
  /** Screen height in rows, used for centering. */
44
47
  readonly screenHeight: number;
45
48
  /** Preferred minimum width — shorter lines are padded but longer lines are not truncated (default: auto from content). */
46
49
  readonly width?: number;
50
+ /** Preferred edge inset from the viewport when the dialog is centered. */
51
+ readonly margin?: number;
47
52
  /** Design token for the border color. */
48
53
  readonly borderToken?: TokenValue;
49
54
  /** Background fill token for the overlay interior. */
@@ -89,6 +94,22 @@ export interface ToastOptions {
89
94
  * @returns Composited string with all overlays applied.
90
95
  */
91
96
  export declare function composite(background: string, overlays: readonly Overlay[], options?: CompositeOptions): string;
97
+ /**
98
+ * Paint one or more overlays onto a background surface.
99
+ *
100
+ * Overlays are applied in array order, with later overlays painting over
101
+ * earlier ones. String-only overlays are normalized through an explicit
102
+ * ANSI-to-surface bridge at the composition edge.
103
+ */
104
+ export declare function compositeSurface(background: Surface, overlays: readonly Overlay[], options?: CompositeOptions): Surface;
105
+ /**
106
+ * Paint overlays onto an existing target surface, optionally starting from an
107
+ * existing background surface.
108
+ *
109
+ * When `background` and `target` are the same object, the surface is
110
+ * composited in place without an extra clone.
111
+ */
112
+ export declare function compositeSurfaceInto(background: Surface, target: Surface, overlays: readonly Overlay[], options?: CompositeOptions): Surface;
92
113
  /**
93
114
  * Create a centered modal dialog overlay.
94
115
  *
@@ -115,8 +136,8 @@ export type DrawerAnchor = 'left' | 'right' | 'top' | 'bottom';
115
136
  * Configuration for the {@link drawer} overlay.
116
137
  */
117
138
  interface DrawerBaseOptions {
118
- /** Content string to display inside the drawer. */
119
- readonly content: string;
139
+ /** Content to display inside the drawer. Accepts plain text or a structured surface. */
140
+ readonly content: OverlayContent;
120
141
  /** Screen width in columns, used for positioning. */
121
142
  readonly screenWidth: number;
122
143
  /** Screen height in rows, used for sizing. */
@@ -179,8 +200,8 @@ export type TooltipDirection = 'top' | 'bottom' | 'left' | 'right';
179
200
  * Configuration for the {@link tooltip} overlay.
180
201
  */
181
202
  export interface TooltipOptions {
182
- /** Content string to display inside the tooltip. */
183
- readonly content: string;
203
+ /** Content to display inside the tooltip. Accepts plain text or a structured surface. */
204
+ readonly content: OverlayContent;
184
205
  /** Row of the target element (0-based). */
185
206
  readonly row: number;
186
207
  /** Column of the target element (0-based). */
@@ -1 +1 @@
1
- {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../src/overlay.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../src/overlay.ts"],"names":[],"mappings":"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"}
package/dist/overlay.js CHANGED
@@ -2,12 +2,14 @@
2
2
  * Overlay compositing primitives for TUI apps.
3
3
  *
4
4
  * - `composite()` — paint overlays onto a background string (ANSI-safe)
5
+ * - `compositeSurface()` — paint overlays onto a background surface
5
6
  * - `modal()` — centered dialog overlay with title, body, hint
6
7
  * - `toast()` — anchored notification overlay with variant icons
7
8
  * - `tooltip()` — positioned overlay relative to a target element
8
9
  */
9
- import { makeBgFill } from '@flyingrobots/bijou';
10
+ import { createSurface, graphemeClusterWidth, parseAnsiToSurface, segmentGraphemes, shouldApplyBg, stripAnsi, surfaceToString, } from '@flyingrobots/bijou';
10
11
  import { sliceAnsi, visibleLength, clipToWidth } from './viewport.js';
12
+ import { clampCenteredPosition, resolveOverlayMargin } from './design-language.js';
11
13
  // ---------------------------------------------------------------------------
12
14
  // Border characters (same as core box.ts)
13
15
  // ---------------------------------------------------------------------------
@@ -78,30 +80,170 @@ export function composite(background, overlays, options) {
78
80
  }
79
81
  return bgLines.join('\n');
80
82
  }
81
- // ---------------------------------------------------------------------------
82
- // renderBox (shared between modal and toast)
83
- // ---------------------------------------------------------------------------
84
83
  /**
85
- * Render lines inside a unicode box border.
84
+ * Paint one or more overlays onto a background surface.
86
85
  *
87
- * Compute inner width from the widest line, then wrap all lines
88
- * with vertical borders and pad to uniform width.
86
+ * Overlays are applied in array order, with later overlays painting over
87
+ * earlier ones. String-only overlays are normalized through an explicit
88
+ * ANSI-to-surface bridge at the composition edge.
89
+ */
90
+ export function compositeSurface(background, overlays, options) {
91
+ const result = background.clone();
92
+ return compositeSurfaceInto(result, result, overlays, options);
93
+ }
94
+ /**
95
+ * Paint overlays onto an existing target surface, optionally starting from an
96
+ * existing background surface.
89
97
  *
90
- * @param lines - Content lines to place inside the box.
91
- * @param borderColor - Function to apply border styling (identity for unstyled).
92
- * @param bgFill - Optional function to wrap interior content with background color styling.
93
- * @returns Box string with top/bottom borders and bordered content lines.
98
+ * When `background` and `target` are the same object, the surface is
99
+ * composited in place without an extra clone.
94
100
  */
95
- function renderBox(lines, borderColor, bgFill) {
96
- const innerWidth = lines.reduce((max, l) => Math.max(max, visibleLength(l)), 0);
97
- const top = borderColor(BORDER.tl + BORDER.h.repeat(innerWidth + 2) + BORDER.tr);
98
- const bottom = borderColor(BORDER.bl + BORDER.h.repeat(innerWidth + 2) + BORDER.br);
99
- const fill = bgFill ?? ((s) => s);
100
- const body = lines.map((l) => {
101
- const pad = innerWidth - visibleLength(l);
102
- return borderColor(BORDER.v) + fill(' ' + l + ' '.repeat(pad) + ' ') + borderColor(BORDER.v);
103
- });
104
- return [top, ...body, bottom].join('\n');
101
+ export function compositeSurfaceInto(background, target, overlays, options) {
102
+ if (target !== background) {
103
+ target.clear();
104
+ target.blit(background, 0, 0);
105
+ }
106
+ if (options?.dim) {
107
+ for (const cell of target.cells) {
108
+ if (cell.empty || cell.char === ' ')
109
+ continue;
110
+ const modifiers = cell.modifiers ?? [];
111
+ if (!modifiers.includes('dim')) {
112
+ cell.modifiers = [...modifiers, 'dim'];
113
+ }
114
+ cell.empty = false;
115
+ }
116
+ }
117
+ for (const overlay of overlays) {
118
+ target.blit(overlay.surface ?? surfaceFromContent(overlay.content), overlay.col, overlay.row);
119
+ }
120
+ return target;
121
+ }
122
+ function styleFromToken(token, ctx) {
123
+ if (!ctx || token == null)
124
+ return {};
125
+ return {
126
+ fg: token.hex,
127
+ bg: token.bg,
128
+ modifiers: token.modifiers ? [...token.modifiers] : undefined,
129
+ };
130
+ }
131
+ function backgroundStyleFromToken(token, ctx) {
132
+ if (!ctx || !shouldApplyBg(ctx) || !token?.bg)
133
+ return {};
134
+ return { bg: token.bg };
135
+ }
136
+ function mergeStyles(base, extra) {
137
+ const modifiers = [...(base.modifiers ?? []), ...(extra.modifiers ?? [])];
138
+ return {
139
+ fg: extra.fg ?? base.fg,
140
+ bg: extra.bg ?? base.bg,
141
+ modifiers: modifiers.length > 0 ? Array.from(new Set(modifiers)) : undefined,
142
+ };
143
+ }
144
+ function plainSurfaceToString(surface) {
145
+ const lines = [];
146
+ for (let y = 0; y < surface.height; y++) {
147
+ let line = '';
148
+ for (let x = 0; x < surface.width; x++) {
149
+ line += surface.get(x, y).char;
150
+ }
151
+ lines.push(line);
152
+ }
153
+ return lines.join('\n');
154
+ }
155
+ function overlayContentFromSurface(surface, ctx) {
156
+ return ctx ? surfaceToString(surface, ctx.style) : plainSurfaceToString(surface);
157
+ }
158
+ function setStyledCell(surface, x, y, char, style) {
159
+ surface.set(x, y, { char, ...style, empty: false });
160
+ }
161
+ function setStyledGrapheme(surface, x, y, char, style) {
162
+ if (x >= surface.width)
163
+ return 0;
164
+ const width = Math.max(1, graphemeClusterWidth(char));
165
+ setStyledCell(surface, x, y, char, style);
166
+ for (let offset = 1; offset < width && x + offset < surface.width; offset++) {
167
+ setStyledCell(surface, x + offset, y, '', style);
168
+ }
169
+ return width;
170
+ }
171
+ function lineSurface(text, style = {}) {
172
+ if (text.length === 0)
173
+ return createSurface(0, 1);
174
+ const plain = stripAnsi(text);
175
+ const width = Math.max(0, visibleLength(text));
176
+ if (width === 0)
177
+ return createSurface(0, 1);
178
+ if (plain !== text && style.fg == null && style.bg == null && style.modifiers == null) {
179
+ return parseAnsiToSurface(text, width, 1);
180
+ }
181
+ const graphemes = segmentGraphemes(plain);
182
+ const surface = createSurface(width, 1);
183
+ let x = 0;
184
+ for (const grapheme of graphemes) {
185
+ if (x >= width)
186
+ break;
187
+ x += setStyledGrapheme(surface, x, 0, grapheme, style);
188
+ }
189
+ return surface;
190
+ }
191
+ function lineWithInheritedBackground(line, bg) {
192
+ if (bg == null || line.width === 0)
193
+ return line;
194
+ const result = line.clone();
195
+ for (let x = 0; x < result.width; x++) {
196
+ const cell = result.get(x, 0);
197
+ if (cell.empty)
198
+ continue;
199
+ result.set(x, 0, { ...cell, bg: cell.bg ?? bg, empty: false });
200
+ }
201
+ return result;
202
+ }
203
+ function surfaceRows(surface, maxWidth) {
204
+ const width = maxWidth != null ? Math.max(0, Math.min(surface.width, maxWidth)) : surface.width;
205
+ if (surface.height <= 0)
206
+ return [createSurface(width, 1)];
207
+ const rows = [];
208
+ for (let y = 0; y < surface.height; y++) {
209
+ const row = createSurface(width, 1);
210
+ if (width > 0)
211
+ row.blit(surface, 0, 0, 0, y, width, 1);
212
+ rows.push(row);
213
+ }
214
+ return rows;
215
+ }
216
+ function contentLines(content, maxWidth) {
217
+ if (typeof content === 'string') {
218
+ return content.split('\n').map((line) => lineSurface(maxWidth != null ? clipToWidth(line, maxWidth) : line));
219
+ }
220
+ return surfaceRows(content, maxWidth);
221
+ }
222
+ function renderBoxSurface(lines, borderStyle, fillStyle, innerWidthOverride) {
223
+ const innerWidth = innerWidthOverride ?? lines.reduce((max, line) => Math.max(max, line.width), 0);
224
+ const width = innerWidth + 4;
225
+ const height = lines.length + 2;
226
+ const surface = createSurface(width, height);
227
+ setStyledCell(surface, 0, 0, BORDER.tl, borderStyle);
228
+ for (let x = 1; x < width - 1; x++)
229
+ setStyledCell(surface, x, 0, BORDER.h, borderStyle);
230
+ setStyledCell(surface, width - 1, 0, BORDER.tr, borderStyle);
231
+ setStyledCell(surface, 0, height - 1, BORDER.bl, borderStyle);
232
+ for (let x = 1; x < width - 1; x++)
233
+ setStyledCell(surface, x, height - 1, BORDER.h, borderStyle);
234
+ setStyledCell(surface, width - 1, height - 1, BORDER.br, borderStyle);
235
+ for (let i = 0; i < lines.length; i++) {
236
+ const y = i + 1;
237
+ setStyledCell(surface, 0, y, BORDER.v, borderStyle);
238
+ for (let x = 1; x < width - 1; x++)
239
+ setStyledCell(surface, x, y, ' ', fillStyle);
240
+ setStyledCell(surface, width - 1, y, BORDER.v, borderStyle);
241
+ const line = lineWithInheritedBackground(lines[i], fillStyle.bg);
242
+ if (line.width > 0 && innerWidth > 0) {
243
+ surface.blit(line, 2, y, 0, 0, Math.min(line.width, innerWidth), 1);
244
+ }
245
+ }
246
+ return surface;
105
247
  }
106
248
  // ---------------------------------------------------------------------------
107
249
  // modal()
@@ -117,41 +259,25 @@ function renderBox(lines, borderColor, bgFill) {
117
259
  */
118
260
  export function modal(options) {
119
261
  const { title, body, hint, screenWidth, screenHeight, ctx } = options;
120
- const contentLines = [];
262
+ const lines = [];
121
263
  if (title != null) {
122
- const titleText = ctx ? ctx.style.bold(title) : title;
123
- contentLines.push(titleText, '');
264
+ lines.push(lineSurface(title, ctx ? { modifiers: ['bold'] } : {}), createSurface(0, 1));
124
265
  }
125
- contentLines.push(...body.split('\n'));
266
+ lines.push(...contentLines(body));
126
267
  if (hint != null) {
127
- contentLines.push('');
128
- const hintText = ctx
129
- ? ctx.style.styled(ctx.semantic('muted'), hint)
130
- : hint;
131
- contentLines.push(hintText);
132
- }
133
- // If width override, constrain inner width
134
- if (options.width != null) {
135
- const targetInner = options.width - 4; // border + padding
136
- // Pad short lines, but don't truncate (user controls width)
137
- for (let i = 0; i < contentLines.length; i++) {
138
- const vis = visibleLength(contentLines[i]);
139
- if (vis < targetInner) {
140
- contentLines[i] = contentLines[i] + ' '.repeat(targetInner - vis);
141
- }
268
+ lines.push(createSurface(0, 1));
269
+ if (typeof hint === 'string') {
270
+ lines.push(lineSurface(hint, styleFromToken(ctx?.semantic('muted'), ctx)));
271
+ }
272
+ else {
273
+ lines.push(...surfaceRows(hint));
142
274
  }
143
275
  }
144
- const borderColor = ctx && options.borderToken
145
- ? (s) => ctx.style.styled(options.borderToken, s)
146
- : (s) => s;
147
- const bgFill = makeBgFill(options.bgToken, ctx);
148
- const boxStr = renderBox(contentLines, borderColor, bgFill);
149
- const boxLines = boxStr.split('\n');
150
- const boxHeight = boxLines.length;
151
- const boxWidth = visibleLength(boxLines[0]);
152
- const row = Math.max(0, Math.floor((screenHeight - boxHeight) / 2));
153
- const col = Math.max(0, Math.floor((screenWidth - boxWidth) / 2));
154
- return { content: boxStr, row, col };
276
+ const surface = renderBoxSurface(lines, styleFromToken(options.borderToken, ctx), backgroundStyleFromToken(options.bgToken, ctx), options.width != null ? Math.max(0, options.width - 4) : undefined);
277
+ const margin = resolveOverlayMargin(screenWidth, screenHeight, options.margin);
278
+ const row = clampCenteredPosition(screenHeight, surface.height, margin);
279
+ const col = clampCenteredPosition(screenWidth, surface.width, margin);
280
+ return { content: overlayContentFromSurface(surface, ctx), surface, row, col };
155
281
  }
156
282
  // ---------------------------------------------------------------------------
157
283
  // toast()
@@ -178,44 +304,34 @@ const TOAST_BORDER = {
178
304
  * @returns Overlay positioned at the specified screen corner.
179
305
  */
180
306
  export function toast(options) {
181
- const { message, variant = 'info', anchor = 'bottom-right', screenWidth, screenHeight, margin = 1, ctx, } = options;
307
+ const { message, variant = 'info', anchor = 'bottom-right', screenWidth, screenHeight, margin, ctx, } = options;
308
+ const resolvedMargin = resolveOverlayMargin(screenWidth, screenHeight, margin);
182
309
  const icon = TOAST_ICONS[variant];
183
- let line = icon + ' ' + message;
184
- if (ctx) {
185
- line = ctx.style.styled(ctx.semantic(variant), line);
186
- }
187
- const borderKey = TOAST_BORDER[variant];
188
- const borderColor = ctx
189
- ? (s) => ctx.style.styled(ctx.border(borderKey), s)
190
- : (s) => s;
191
- const bgFill = makeBgFill(options.bgToken, ctx);
192
- const boxStr = renderBox([line], borderColor, bgFill);
193
- const boxLines = boxStr.split('\n');
194
- const boxHeight = boxLines.length;
195
- const boxWidth = visibleLength(boxLines[0]);
310
+ const line = lineSurface(`${icon} ${message}`, styleFromToken(ctx?.semantic(variant), ctx));
311
+ const surface = renderBoxSurface([line], styleFromToken(ctx?.border(TOAST_BORDER[variant]), ctx), backgroundStyleFromToken(options.bgToken, ctx));
196
312
  let row;
197
313
  let col;
198
314
  switch (anchor) {
199
315
  case 'top-right':
200
- row = margin;
201
- col = screenWidth - boxWidth - margin;
316
+ row = resolvedMargin;
317
+ col = screenWidth - surface.width - resolvedMargin;
202
318
  break;
203
319
  case 'bottom-right':
204
- row = screenHeight - boxHeight - margin;
205
- col = screenWidth - boxWidth - margin;
320
+ row = screenHeight - surface.height - resolvedMargin;
321
+ col = screenWidth - surface.width - resolvedMargin;
206
322
  break;
207
323
  case 'bottom-left':
208
- row = screenHeight - boxHeight - margin;
209
- col = margin;
324
+ row = screenHeight - surface.height - resolvedMargin;
325
+ col = resolvedMargin;
210
326
  break;
211
327
  case 'top-left':
212
- row = margin;
213
- col = margin;
328
+ row = resolvedMargin;
329
+ col = resolvedMargin;
214
330
  break;
215
331
  }
216
332
  row = Math.max(0, row);
217
333
  col = Math.max(0, col);
218
- return { content: boxStr, row, col };
334
+ return { content: overlayContentFromSurface(surface, ctx), surface, row, col };
219
335
  }
220
336
  // ---------------------------------------------------------------------------
221
337
  // drawer()
@@ -240,51 +356,54 @@ export function drawer(options) {
240
356
  const dims = resolveDrawerDimensions(options, region);
241
357
  const { width, height, row, col } = dims;
242
358
  const innerWidth = Math.max(0, width - 4); // border + padding on each side
243
- const borderColor = ctx && options.borderToken
244
- ? (s) => ctx.style.styled(options.borderToken, s)
245
- : (s) => s;
246
- const bgFill = makeBgFill(options.bgToken, ctx);
247
- const fill = bgFill ?? ((s) => s);
248
- // Build top border
249
- let topInner;
250
- if (title) {
251
- const titleText = ctx ? ctx.style.bold(title) : title;
252
- const titleVis = visibleLength(titleText);
253
- const remaining = Math.max(0, innerWidth + 2 - titleVis - 2); // +2 for padding around border, -2 for spaces around title
254
- const leftDash = Math.floor(remaining / 2);
255
- const rightDash = remaining - leftDash;
256
- topInner = BORDER.h.repeat(leftDash) + ' ' + titleText + ' ' + BORDER.h.repeat(rightDash);
359
+ const borderStyle = styleFromToken(options.borderToken, ctx);
360
+ const fillStyle = backgroundStyleFromToken(options.bgToken, ctx);
361
+ const titleStyle = ctx ? { modifiers: ['bold'] } : {};
362
+ const surface = createSurface(width, height);
363
+ if (width === 0 || height === 0) {
364
+ return { content: overlayContentFromSurface(surface, ctx), surface, row, col };
257
365
  }
258
- else {
259
- topInner = BORDER.h.repeat(innerWidth + 2);
366
+ for (let x = 0; x < width; x++) {
367
+ const topChar = x === 0 ? BORDER.tl : x === width - 1 ? BORDER.tr : BORDER.h;
368
+ setStyledCell(surface, x, 0, topChar, borderStyle);
369
+ if (height > 1) {
370
+ const bottomChar = x === 0 ? BORDER.bl : x === width - 1 ? BORDER.br : BORDER.h;
371
+ setStyledCell(surface, x, height - 1, bottomChar, borderStyle);
372
+ }
260
373
  }
261
- const topLine = fitLineExact(borderColor(BORDER.tl + topInner + BORDER.tr), width);
262
- const bottomLine = fitLineExact(borderColor(BORDER.bl + BORDER.h.repeat(innerWidth + 2) + BORDER.br), width);
263
- // Build content lines
264
- const contentLines = content.split('\n');
265
- const availableHeight = Math.max(0, height - 2); // minus top + bottom border
266
- const bodyLines = [];
267
- for (let i = 0; i < availableHeight; i++) {
268
- const raw = contentLines[i] ?? '';
269
- const vis = visibleLength(raw);
270
- let clipped;
271
- if (vis > innerWidth) {
272
- clipped = clipToWidth(raw, innerWidth);
374
+ if (title != null && width > 2) {
375
+ const titleLine = lineSurface(title, titleStyle);
376
+ const spanWidth = Math.min(width - 2, titleLine.width + 2);
377
+ const startX = 1 + Math.max(0, Math.floor(((width - 2) - spanWidth) / 2));
378
+ setStyledCell(surface, startX, 0, ' ', borderStyle);
379
+ const mergedTitleStyle = mergeStyles(borderStyle, titleStyle);
380
+ for (let x = 0; x < Math.min(titleLine.width, Math.max(0, spanWidth - 2)); x++) {
381
+ setStyledCell(surface, startX + 1 + x, 0, titleLine.get(x, 0).char, mergedTitleStyle);
273
382
  }
274
- else {
275
- clipped = raw + ' '.repeat(innerWidth - vis);
383
+ if (spanWidth >= 2) {
384
+ setStyledCell(surface, startX + spanWidth - 1, 0, ' ', borderStyle);
276
385
  }
277
- bodyLines.push(fitLineExact(borderColor(BORDER.v) + fill(' ' + clipped + ' ') + borderColor(BORDER.v), width));
278
386
  }
279
- const allLines = [];
280
- if (height === 1) {
281
- allLines.push(topLine);
282
- }
283
- else if (height >= 2) {
284
- allLines.push(topLine, ...bodyLines, bottomLine);
387
+ const rows = contentLines(content, innerWidth);
388
+ const availableHeight = Math.max(0, height - 2);
389
+ for (let i = 0; i < availableHeight; i++) {
390
+ const y = i + 1;
391
+ if (width >= 1)
392
+ setStyledCell(surface, 0, y, BORDER.v, borderStyle);
393
+ for (let x = 1; x < Math.max(1, width - 1); x++) {
394
+ if (x < width - 1)
395
+ setStyledCell(surface, x, y, ' ', fillStyle);
396
+ }
397
+ if (width >= 2)
398
+ setStyledCell(surface, width - 1, y, BORDER.v, borderStyle);
399
+ const line = lineWithInheritedBackground(rows[i] ?? createSurface(0, 1), fillStyle.bg);
400
+ if (line.width > 0 && innerWidth > 0 && width >= 4) {
401
+ surface.blit(line, 2, y, 0, 0, Math.min(line.width, innerWidth), 1);
402
+ }
285
403
  }
286
404
  return {
287
- content: allLines.join('\n'),
405
+ content: overlayContentFromSurface(surface, ctx),
406
+ surface,
288
407
  row,
289
408
  col,
290
409
  };
@@ -348,13 +467,6 @@ function clampRegion(region, screenWidth, screenHeight) {
348
467
  height: Math.floor(clamp(region.height, 0, maxHeight)),
349
468
  };
350
469
  }
351
- /** Clip or pad a single line to exactly `width` visible columns. */
352
- function fitLineExact(line, width) {
353
- const target = Math.max(0, width);
354
- const clipped = clipToWidth(line, target);
355
- const vis = visibleLength(clipped);
356
- return clipped + ' '.repeat(Math.max(0, target - vis));
357
- }
358
470
  /** Clamp a number between min and max. */
359
471
  function clamp(value, min, max) {
360
472
  return Math.max(min, Math.min(max, value));
@@ -374,38 +486,37 @@ function clamp(value, min, max) {
374
486
  export function tooltip(options) {
375
487
  const { content, row: targetRow, col: targetCol, direction = 'top', screenWidth, screenHeight, borderToken, bgToken, ctx, } = options;
376
488
  const maxContentWidth = Math.max(0, screenWidth - 4);
377
- const contentLines = content.split('\n').map((l) => clipToWidth(l, maxContentWidth));
378
- const borderColor = ctx && borderToken
379
- ? (s) => ctx.style.styled(borderToken, s)
380
- : (s) => s;
381
- const bgFill = makeBgFill(bgToken, ctx);
382
- const boxStr = renderBox(contentLines, borderColor, bgFill);
383
- const boxLines = boxStr.split('\n');
384
- const boxHeight = boxLines.length;
385
- const boxWidth = visibleLength(boxLines[0]);
489
+ const lines = contentLines(content, maxContentWidth);
490
+ const surface = renderBoxSurface(lines, styleFromToken(borderToken, ctx), backgroundStyleFromToken(bgToken, ctx));
386
491
  let tipRow;
387
492
  let tipCol;
388
493
  switch (direction) {
389
494
  case 'top':
390
- tipRow = targetRow - boxHeight;
391
- tipCol = targetCol - Math.floor(boxWidth / 2);
495
+ tipRow = targetRow - surface.height;
496
+ tipCol = targetCol - Math.floor(surface.width / 2);
392
497
  break;
393
498
  case 'bottom':
394
499
  tipRow = targetRow + 1;
395
- tipCol = targetCol - Math.floor(boxWidth / 2);
500
+ tipCol = targetCol - Math.floor(surface.width / 2);
396
501
  break;
397
502
  case 'left':
398
- tipRow = targetRow - Math.floor(boxHeight / 2);
399
- tipCol = targetCol - boxWidth;
503
+ tipRow = targetRow - Math.floor(surface.height / 2);
504
+ tipCol = targetCol - surface.width;
400
505
  break;
401
506
  case 'right':
402
- tipRow = targetRow - Math.floor(boxHeight / 2);
507
+ tipRow = targetRow - Math.floor(surface.height / 2);
403
508
  tipCol = targetCol + 1;
404
509
  break;
405
510
  }
406
511
  // Clamp to screen bounds
407
- tipRow = Math.max(0, Math.min(tipRow, screenHeight - boxHeight));
408
- tipCol = Math.max(0, Math.min(tipCol, screenWidth - boxWidth));
409
- return { content: boxStr, row: tipRow, col: tipCol };
512
+ tipRow = Math.max(0, Math.min(tipRow, screenHeight - surface.height));
513
+ tipCol = Math.max(0, Math.min(tipCol, screenWidth - surface.width));
514
+ return { content: overlayContentFromSurface(surface, ctx), surface, row: tipRow, col: tipCol };
515
+ }
516
+ function surfaceFromContent(content) {
517
+ const lines = content.split('\n');
518
+ const width = Math.max(1, ...lines.map((line) => visibleLength(line)));
519
+ const height = Math.max(1, lines.length);
520
+ return parseAnsiToSurface(content, width, height);
410
521
  }
411
522
  //# sourceMappingURL=overlay.js.map