@alchemy.run/sigil 0.0.0-alpha.5 → 0.0.0-alpha.6

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.
@@ -100,7 +100,7 @@ type BoxGlyphs = {
100
100
  //#endregion
101
101
  //#region src/styles.d.ts
102
102
  type Styles = {
103
- readonly textWrap?: "wrap" | "hard" | "truncate-end" | "truncate" | "truncate-middle" | "truncate-start";
103
+ readonly textWrap?: "wrap" | "hard" | "truncate-end" | "truncate" | "truncate-middle" | "truncate-start" | "none";
104
104
  /**
105
105
  Controls how the element is positioned.
106
106
 
@@ -439,7 +439,7 @@ type Props = {
439
439
  */
440
440
  readonly inverse?: boolean;
441
441
  /**
442
- This property tells Ink to wrap or truncate text if its width is larger than the container. If `wrap` is passed (the default), Ink will wrap text and split it into multiple lines. If `hard` is passed, Ink will fill each line to the full column width, breaking words as necessary. If `truncate-*` is passed, Ink will truncate text instead, resulting in one line of text with the rest cut off.
442
+ This property tells Ink to wrap or truncate text if its width is larger than the container. If `wrap` is passed (the default), Ink will wrap text and split it into multiple lines. If `hard` is passed, Ink will fill each line to the full column width, breaking words as necessary. If `truncate-*` is passed, Ink will truncate text instead, resulting in one line of text with the rest cut off. If `none` is passed, the text is neither wrapped nor truncated — it overflows the container (and the screen's nominal width), which is intended for log lines committed to scrollback (e.g. inside `<Static>`), not for live regions.
443
443
  */
444
444
  readonly wrap?: Styles["textWrap"];
445
445
  readonly children?: ReactNode;
@@ -1,6 +1,6 @@
1
1
  import { a as StyledChar } from "./tokenize-Dx1y_l5H.js";
2
2
  import { i as Cell, t as ColorProfile } from "./color-profile-u0Nhe9Nv.js";
3
- import { n as Screen, t as Line } from "./screen-BReKIheE.js";
3
+ import { n as Screen, t as Line } from "./screen-BOSLQ8fF.js";
4
4
  //#region src/screen/ansi.d.ts
5
5
  /** Converts the renderer's canonical ANSI state into backend-neutral cells. */
6
6
  declare function cellsFromAnsi(text: string): Cell[];
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import { a as getTerminalQuery, i as applyTerminalQuery, n as TerminalQueryOptio
4
4
  import { i as OutputStream, n as capabilities, r as getCapabilities, t as CapabilitiesStore } from "./store-CgrG9K4y.js";
5
5
  import { s as CellStyle, t as ColorProfile } from "./color-profile-u0Nhe9Nv.js";
6
6
  import { s as Paint } from "./paint-C19minOS.js";
7
- import { n as Text, r as Styles, t as Props$8 } from "./Text-D5HUf3Fj.js";
7
+ import { n as Text, r as Styles, t as Props$8 } from "./Text-BobFKi74.js";
8
8
  import { i as Node } from "./index-DDVME65c.js";
9
9
  import { t as CursorPosition } from "./cursor-position-D2LAkRG0.js";
10
10
  import { Writable } from "node:stream";
package/dist/index.js CHANGED
@@ -2,17 +2,17 @@ import { C as bsu, S as ansiEscapes, V as esu, W as link, _ as CSI } from "./sgr
2
2
  import { h as tokenizeAnsi, p as graphemes } from "./tokenize-AjqbvtiT.js";
3
3
  import { t as stringWidth } from "./string-width-CijQwpIk.js";
4
4
  import { n as sliceAnsi, r as wrapAnsi } from "./truncate-D31fhU6i.js";
5
- import { _ as squashTextNodes, a as reconciler, c as kittyModifiers, d as FocusContext, f as Text, g as transformAnsiLine, h as emitLayoutListeners, i as useStdinContext, l as resolveFlags, m as createNode, n as useInput, o as detectKittySupport, p as addLayoutListener, r as useStdin, s as kittyFlags, t as useFocus, u as StdinContext, v as accessibilityContext } from "./use-focus-C2sciZOo.js";
5
+ import { _ as squashTextNodes, a as reconciler, c as kittyModifiers, d as FocusContext, f as Text, g as transformAnsiLine, h as emitLayoutListeners, i as useStdinContext, l as resolveFlags, m as createNode, n as useInput, o as detectKittySupport, p as addLayoutListener, r as useStdin, s as kittyFlags, t as useFocus, u as StdinContext, v as accessibilityContext } from "./use-focus-BzqAJi0n.js";
6
6
  import { a as isTty, i as isSigilDev, o as isWindows, r as isScreenReader, t as isInCi } from "./env-YVw64yZS.js";
7
7
  import { a as detectTerminal, c as signalExit, i as detectHyperlinkSupport, n as detectCapabilities, o as detectUnicodeSupport, r as detectColorLevel, s as terminalSize, t as createSupportsColor } from "./detect-BuTXtY6e.js";
8
8
  import { u as cliCursor } from "./osc-CCH7xDoS.js";
9
9
  import { a as registerTerminalIntegration, c as ensureTerminalQuery, d as queryTerminal, f as refreshTerminalQuery, i as getCapabilities, l as getTerminalQuery, o as getRawModeStream, r as capabilities, s as applyTerminalQuery, u as getTerminalQueryPromise } from "./color-policy-SVj1pYTA.js";
10
- import { a as buildCursorSuffix, c as hideCursorEscape, i as buildCursorOnlySequence, o as buildReturnToBottomPrefix, s as cursorPositionChanged, t as TerminalSession } from "./session-BJmzX2NJ.js";
10
+ import { a as buildCursorSuffix, c as hideCursorEscape, i as buildCursorOnlySequence, o as buildReturnToBottomPrefix, s as cursorPositionChanged, t as TerminalSession } from "./session-aZr9O8h3.js";
11
11
  import { t as Yoga } from "./yoga-5jKhYCJC.js";
12
12
  import { r as createCell, t as cellAttributes } from "./cell-_ZVhbfl0.js";
13
13
  import { i as semanticTextStyleToCellStyle, n as mergeSemanticTextStyles, t as emptySemanticTextStyle } from "./semantic-text-style-DIMzC7xt.js";
14
14
  import { a as samplePaint } from "./sample-Cqw1bjUL.js";
15
- import { n as cellFromStyledChar, r as cellsFromAnsi, t as Screen } from "./screen-BOh__-65.js";
15
+ import { n as cellFromStyledChar, r as cellsFromAnsi, t as Screen } from "./screen-CiPytswf.js";
16
16
  import { t as colorProfileFromLevel } from "./color-profile-DHhQHY55.js";
17
17
  import { n as serializeScreen, t as serializeLine } from "./serialize-BTkAZgw1.js";
18
18
  import { t as stripAnsi } from "./strip-BvU4toXG.js";
@@ -1115,6 +1115,7 @@ function structuredTextBaseStyle(node) {
1115
1115
  }
1116
1116
  /** Wraps or truncates semantic cells without converting them through ANSI. */
1117
1117
  function wrapStructuredText(input, maxWidth, wrap, baseStyle) {
1118
+ if (wrap === "none") return input.map((line) => [...line]);
1118
1119
  if (maxWidth < 1) return [[]];
1119
1120
  return input.flatMap((line) => {
1120
1121
  if (lineWidth(line) <= maxWidth) return [[...line]];
@@ -1301,7 +1302,9 @@ const paintTree = (node, output, options) => {
1301
1302
  const firstChildYoga = node.childNodes[0]?.yogaNode;
1302
1303
  const paddingX = firstChildYoga?.getComputedLeft() ?? 0;
1303
1304
  const paddingY = firstChildYoga?.getComputedTop() ?? 0;
1304
- const lines = wrapStructuredText(structuredTextLines(node), maxWidth, node.style.textWrap ?? "wrap", structuredTextBaseStyle(node));
1305
+ const textWrap = node.style.textWrap ?? "wrap";
1306
+ const overflow = textWrap === "none";
1307
+ const lines = wrapStructuredText(structuredTextLines(node), maxWidth, textWrap, structuredTextBaseStyle(node));
1305
1308
  const paintBounds = {
1306
1309
  x: x + paddingX,
1307
1310
  y: y + paddingY,
@@ -1314,8 +1317,11 @@ const paintTree = (node, output, options) => {
1314
1317
  output.writeAnsi(paintBounds.x, paintBounds.y, sampled.map((line) => serializeLine(line, {
1315
1318
  colorProfile: output.paintContext.profile ?? "truecolor",
1316
1319
  trimEnd: false
1317
- })).join("\n"), { transformers: textTransformers });
1318
- } else output.writeCells(paintBounds.x, paintBounds.y, sampled);
1320
+ })).join("\n"), {
1321
+ transformers: textTransformers,
1322
+ overflow
1323
+ });
1324
+ } else output.writeCells(paintBounds.x, paintBounds.y, sampled, { overflow });
1319
1325
  }
1320
1326
  return;
1321
1327
  }
@@ -1375,15 +1381,16 @@ var Canvas = class {
1375
1381
  };
1376
1382
  this.#screen = new Screen(this.width, this.height);
1377
1383
  }
1378
- writeCells(x, y, lines) {
1384
+ writeCells(x, y, lines, options) {
1379
1385
  const clip = this.#clips.at(-1);
1386
+ const overflow = options?.overflow === true;
1380
1387
  for (const [rowOffset, line] of lines.entries()) {
1381
1388
  const currentY = y + rowOffset;
1382
1389
  if (!this.#insideY(currentY, clip)) continue;
1383
1390
  let currentX = x;
1384
1391
  for (const cell of line) {
1385
1392
  const endX = currentX + cell.width;
1386
- if (!(clip?.x1 !== void 0 && currentX < clip.x1 || clip?.x2 !== void 0 && endX > clip.x2)) this.#composeNativeCell(currentX, currentY, cell);
1393
+ if (!(clip?.x1 !== void 0 && currentX < clip.x1 || clip?.x2 !== void 0 && endX > clip.x2)) this.#composeNativeCell(currentX, currentY, cell, overflow);
1387
1394
  currentX = endX;
1388
1395
  }
1389
1396
  }
@@ -1392,6 +1399,7 @@ var Canvas = class {
1392
1399
  if (!text) return;
1393
1400
  let lines = text.split("\n");
1394
1401
  const clip = this.#clips.at(-1);
1402
+ const overflow = options.overflow === true;
1395
1403
  if (clip?.y1 !== void 0 && y < clip.y1) {
1396
1404
  lines = lines.slice(clip.y1 - y);
1397
1405
  y = clip.y1;
@@ -1410,7 +1418,7 @@ var Canvas = class {
1410
1418
  for (const character of transformAnsiLine(line, lineIndex, options.transformers)) {
1411
1419
  const width = Math.max(1, stringWidth(character.value));
1412
1420
  if (clip?.x2 !== void 0 && currentX + width > clip.x2) break;
1413
- this.#writeCompatibilityCell(currentX, currentY, character, width);
1421
+ this.#writeCompatibilityCell(currentX, currentY, character, width, overflow);
1414
1422
  currentX += width;
1415
1423
  }
1416
1424
  }
@@ -1427,8 +1435,8 @@ var Canvas = class {
1427
1435
  #insideY(y, clip) {
1428
1436
  return y >= 0 && y < this.#screen.height && (clip?.y1 === void 0 || y >= clip.y1) && (clip?.y2 === void 0 || y < clip.y2);
1429
1437
  }
1430
- #composeNativeCell(x, y, cell) {
1431
- if (x >= this.#screen.width) return;
1438
+ #composeNativeCell(x, y, cell, overflow = false) {
1439
+ if (!overflow && x >= this.#screen.width) return;
1432
1440
  this.#screen.composeCell(x, y, {
1433
1441
  content: {
1434
1442
  grapheme: cell.grapheme,
@@ -1440,10 +1448,10 @@ var Canvas = class {
1440
1448
  underline: cell.style.underline,
1441
1449
  attributes: cell.style.attributes,
1442
1450
  hyperlink: cell.hyperlink ?? null
1443
- });
1451
+ }, { overflow });
1444
1452
  }
1445
- #writeCompatibilityCell(x, y, character, width) {
1446
- if (x >= this.#screen.width) return;
1453
+ #writeCompatibilityCell(x, y, character, width, overflow = false) {
1454
+ if (!overflow && x >= this.#screen.width) return;
1447
1455
  const cell = cellFromStyledChar(character, width);
1448
1456
  this.#screen.composeCell(x, y, {
1449
1457
  content: {
@@ -1456,7 +1464,7 @@ var Canvas = class {
1456
1464
  underline: cell.style.underline,
1457
1465
  attributes: cell.style.attributes,
1458
1466
  hyperlink: cell.hyperlink ?? null
1459
- });
1467
+ }, { overflow });
1460
1468
  }
1461
1469
  };
1462
1470
  //#endregion
package/dist/router.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as Props } from "./Text-D5HUf3Fj.js";
1
+ import { t as Props } from "./Text-BobFKi74.js";
2
2
  import { ReactElement, ReactNode } from "react";
3
3
  //#region src/router/history.d.ts
4
4
  /**
package/dist/router.js CHANGED
@@ -1,4 +1,4 @@
1
- import { f as Text, n as useInput, t as useFocus } from "./use-focus-C2sciZOo.js";
1
+ import { f as Text, n as useInput, t as useFocus } from "./use-focus-BzqAJi0n.js";
2
2
  import { Children, Fragment, createContext, isValidElement, startTransition, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
3
3
  import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
4
4
  //#region src/router/history.ts
@@ -16,17 +16,26 @@ declare class Screen {
16
16
  #private;
17
17
  constructor(width: number, height: number);
18
18
  get width(): number;
19
+ /**
20
+ The populated length of one row: the nominal width, or more when overflow
21
+ writes (`textWrap: "none"` content) extended it past the screen's width.
22
+ */
23
+ rowLength(y: number): number;
19
24
  get height(): number;
20
25
  bounds(): Rect;
21
26
  cellAt(x: number, y: number): Cell | undefined;
22
27
  /** Whether a drawable explicitly touched this position. */
23
28
  isPainted(x: number, y: number): boolean;
24
- setCell(x: number, y: number, cell: Cell): void;
29
+ setCell(x: number, y: number, cell: Cell, options?: {
30
+ overflow?: boolean;
31
+ }): void;
25
32
  /**
26
33
  Composes independent cell channels over the existing cell. An undefined
27
34
  patch is transparent; an explicit space in `content` paints a blank.
28
35
  */
29
- composeCell(x: number, y: number, patch: CellPatch | undefined): void;
36
+ composeCell(x: number, y: number, patch: CellPatch | undefined, options?: {
37
+ overflow?: boolean;
38
+ }): void;
30
39
  /** Composes a patch throughout a rectangle, clipped to this screen. */
31
40
  fill(bounds: Rect, patch: CellPatch | undefined): void;
32
41
  /** Resizes while preserving complete graphemes in the shared top-left area. */
@@ -155,6 +155,13 @@ var Screen = class {
155
155
  get width() {
156
156
  return this.#width;
157
157
  }
158
+ /**
159
+ The populated length of one row: the nominal width, or more when overflow
160
+ writes (`textWrap: "none"` content) extended it past the screen's width.
161
+ */
162
+ rowLength(y) {
163
+ return Math.max(this.#width, this.#rows[y]?.length ?? 0);
164
+ }
158
165
  get height() {
159
166
  return this.#height;
160
167
  }
@@ -173,17 +180,20 @@ var Screen = class {
173
180
  isPainted(x, y) {
174
181
  return this.#painted[y]?.[x] ?? false;
175
182
  }
176
- setCell(x, y, cell) {
183
+ setCell(x, y, cell, options) {
177
184
  const row = this.#rows[y];
178
- if (!row || x < 0 || x >= this.width) return;
185
+ const overflow = options?.overflow === true;
186
+ if (!row || x < 0 || !overflow && x >= this.width) return;
179
187
  if (!Number.isInteger(cell.width) || cell.width < 1) throw new Error("Only leading cells with a positive width may be written");
180
188
  const finalColumn = x + cell.width;
181
- const clipped = finalColumn > this.width;
182
- const overwriteEnd = Math.min(finalColumn, this.width);
189
+ if (overflow && finalColumn > row.length) this.#growRow(row, y, finalColumn);
190
+ const capacity = overflow ? row.length : this.width;
191
+ const clipped = finalColumn > capacity;
192
+ const overwriteEnd = Math.min(finalColumn, capacity);
183
193
  for (let column = x; column < overwriteEnd; column++) this.#clearGraphemeAt(row, y, column);
184
194
  if (clipped) {
185
- for (let column = x; column < this.width; column++) row[column] = emptyCell;
186
- this.#markDirty(y, x, this.width);
195
+ for (let column = x; column < capacity; column++) row[column] = emptyCell;
196
+ this.#markDirty(y, x, capacity);
187
197
  return;
188
198
  }
189
199
  row[x] = cell;
@@ -203,10 +213,10 @@ var Screen = class {
203
213
  Composes independent cell channels over the existing cell. An undefined
204
214
  patch is transparent; an explicit space in `content` paints a blank.
205
215
  */
206
- composeCell(x, y, patch) {
216
+ composeCell(x, y, patch, options) {
207
217
  if (!patch) return;
208
218
  const row = this.#rows[y];
209
- if (!row || x < 0 || x >= this.width) return;
219
+ if (!row || x < 0 || options?.overflow !== true && x >= this.width) return;
210
220
  let leadingColumn = x;
211
221
  while (leadingColumn > 0 && row[leadingColumn]?.width === 0) leadingColumn--;
212
222
  const destination = row[leadingColumn] ?? emptyCell;
@@ -220,7 +230,7 @@ var Screen = class {
220
230
  };
221
231
  const content = patch.content ?? destination;
222
232
  const hyperlink = patch.hyperlink === void 0 ? destination.hyperlink : patch.hyperlink ?? void 0;
223
- this.setCell(writeColumn, y, createCell(content.grapheme, content.width, style, hyperlink));
233
+ this.setCell(writeColumn, y, createCell(content.grapheme, content.width, style, hyperlink), options);
224
234
  }
225
235
  /** Composes a patch throughout a rectangle, clipped to this screen. */
226
236
  fill(bounds, patch) {
@@ -265,7 +275,7 @@ var Screen = class {
265
275
  for (const [y, row] of this.#rows.entries()) {
266
276
  row.fill(emptyCell);
267
277
  this.#painted[y].fill(true);
268
- this.#markDirty(y, 0, this.width);
278
+ this.#markDirty(y, 0, this.rowLength(y));
269
279
  }
270
280
  }
271
281
  toRows() {
@@ -282,6 +292,14 @@ var Screen = class {
282
292
  this.#dirty.fill(void 0);
283
293
  return spans;
284
294
  }
295
+ /** Extends a row (and its paint mask) so overflow writes land past `width`. */
296
+ #growRow(row, y, length) {
297
+ const painted = this.#painted[y];
298
+ while (row.length < length) {
299
+ row.push(emptyCell);
300
+ painted.push(false);
301
+ }
302
+ }
285
303
  #clearGraphemeAt(row, y, x) {
286
304
  const existing = row[x];
287
305
  if (!existing || existing === emptyCell) return;
@@ -289,7 +307,7 @@ var Screen = class {
289
307
  while (leadingColumn > 0 && row[leadingColumn]?.width === 0) leadingColumn--;
290
308
  const leadingCell = row[leadingColumn];
291
309
  const width = Math.max(1, leadingCell?.width ?? 1);
292
- for (let column = leadingColumn; column < Math.min(leadingColumn + width, this.width); column++) {
310
+ for (let column = leadingColumn; column < Math.min(leadingColumn + width, row.length); column++) {
293
311
  row[column] = emptyCell;
294
312
  this.#painted[y][column] = true;
295
313
  }
@@ -306,7 +324,7 @@ var Screen = class {
306
324
  #markDirty(y, start, end) {
307
325
  if (y < 0 || y >= this.height || start >= end) return;
308
326
  const clippedStart = Math.max(0, start);
309
- const clippedEnd = Math.min(this.width, end);
327
+ const clippedEnd = Math.min(this.rowLength(y), end);
310
328
  const current = this.#dirty[y];
311
329
  this.#dirty[y] = current ? {
312
330
  start: Math.min(current.start, clippedStart),
package/dist/screen.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { _ as emptyCellStyle, a as CellContent, c as Color, d as RgbColor, f as UnderlineStyle, g as emptyCell, h as createCell, i as Cell, l as Hyperlink, m as cellsEqual, n as colorProfileFromLevel, o as CellPatch, p as cellAttributes, r as quantizeColor, s as CellStyle, t as ColorProfile, u as IndexedColor } from "./color-profile-u0Nhe9Nv.js";
2
2
  import { n as Rect, t as Point } from "./geometry-BxXOzJgo.js";
3
- import { a as cellFromStyledChar, i as styledCharFromCell, n as serializeLine, o as cellsFromAnsi, r as serializeScreen, t as SerializeOptions } from "./index-Bmc2tRPk.js";
4
- import { n as Screen, t as Line } from "./screen-BReKIheE.js";
3
+ import { a as cellFromStyledChar, i as styledCharFromCell, n as serializeLine, o as cellsFromAnsi, r as serializeScreen, t as SerializeOptions } from "./index-DZ88EXJv.js";
4
+ import { n as Screen, t as Line } from "./screen-BOSLQ8fF.js";
5
5
  export { Cell, CellContent, CellPatch, CellStyle, Color, ColorProfile, Hyperlink, IndexedColor, type Line, Point, Rect, RgbColor, Screen, SerializeOptions, UnderlineStyle, cellAttributes, cellFromStyledChar, cellsEqual, cellsFromAnsi, colorProfileFromLevel, createCell, emptyCell, emptyCellStyle, quantizeColor, serializeLine, serializeScreen, styledCharFromCell };
package/dist/screen.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as emptyCellStyle, i as emptyCell, n as cellsEqual, r as createCell, t as cellAttributes } from "./cell-_ZVhbfl0.js";
2
- import { n as cellFromStyledChar, r as cellsFromAnsi, t as Screen } from "./screen-BOh__-65.js";
2
+ import { n as cellFromStyledChar, r as cellsFromAnsi, t as Screen } from "./screen-CiPytswf.js";
3
3
  import { n as quantizeColor, t as colorProfileFromLevel } from "./color-profile-DHhQHY55.js";
4
4
  import { n as serializeScreen, r as styledCharFromCell, t as serializeLine } from "./serialize-BTkAZgw1.js";
5
5
  export { Screen, cellAttributes, cellFromStyledChar, cellsEqual, cellsFromAnsi, colorProfileFromLevel, createCell, emptyCell, emptyCellStyle, quantizeColor, serializeLine, serializeScreen, styledCharFromCell };
@@ -358,12 +358,13 @@ function findFirstChangedRow(previous, next) {
358
358
  for (let y = 0; y < height; y++) if (!rowsEqual(previous, next, y)) return y;
359
359
  }
360
360
  function rowsEqual(left, right, y) {
361
- for (let x = 0; x < left.width; x++) if (!cellsEqual(left.cellAt(x, y), right.cellAt(x, y))) return false;
361
+ const length = Math.max(left.rowLength(y), right.rowLength(y));
362
+ for (let x = 0; x < length; x++) if (!cellsEqual(left.cellAt(x, y), right.cellAt(x, y))) return false;
362
363
  return true;
363
364
  }
364
365
  function screensEqual(left, right) {
365
366
  if (left.width !== right.width || left.height !== right.height) return false;
366
- for (let y = 0; y < left.height; y++) for (let x = 0; x < left.width; x++) if (!cellsEqual(left.cellAt(x, y), right.cellAt(x, y))) return false;
367
+ for (let y = 0; y < left.height; y++) if (!rowsEqual(left, right, y)) return false;
367
368
  return true;
368
369
  }
369
370
  //#endregion
@@ -3,7 +3,7 @@ import { n as TerminalProgressState, t as ClipboardSelection } from "./osc-Cn0fw
3
3
  import { i as OutputStream, t as CapabilitiesStore } from "./store-CgrG9K4y.js";
4
4
  import { t as ColorProfile } from "./color-profile-u0Nhe9Nv.js";
5
5
  import { t as ColorPolicy } from "./color-policy-BMzMwV7Q.js";
6
- import { n as Screen } from "./screen-BReKIheE.js";
6
+ import { n as Screen } from "./screen-BOSLQ8fF.js";
7
7
  import { t as CursorPosition } from "./cursor-position-D2LAkRG0.js";
8
8
  //#region src/input-parser.d.ts
9
9
  type InputEvent = string | {
package/dist/terminal.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as TerminalInput, r as parseMouseEvent, t as TerminalSession } from "./session-BJmzX2NJ.js";
1
+ import { n as TerminalInput, r as parseMouseEvent, t as TerminalSession } from "./session-aZr9O8h3.js";
2
2
  export { TerminalInput, TerminalSession, parseMouseEvent };
@@ -278,6 +278,7 @@ function transformAnsiLine(serializedSubtree, lineIndex, transformers) {
278
278
  //#region src/wrap-text.ts
279
279
  const wrapTextCache = new QuickLru({ maxSize: 4096 });
280
280
  const wrapText = (text, maxWidth, wrapType) => {
281
+ if (wrapType === "none") return text;
281
282
  const cacheKey = text + String(maxWidth) + String(wrapType);
282
283
  const cachedText = wrapTextCache.get(cacheKey);
283
284
  if (cachedText !== void 0) return cachedText;
@@ -386,6 +387,7 @@ const measureTextNode = function(node, width) {
386
387
  if (dimensions.width <= width) return dimensions;
387
388
  if (dimensions.width >= 1 && width > 0 && width < 1) return dimensions;
388
389
  const textWrap = node.style?.textWrap ?? "wrap";
390
+ if (textWrap === "none") return dimensions;
389
391
  const wrappedText = wrapText(text, width, textWrap);
390
392
  return measureText(wrappedText);
391
393
  };
@@ -562,7 +564,7 @@ const detectKittySupport = (stdin, stdout, onSupported) => {
562
564
  //#endregion
563
565
  //#region package.json
564
566
  var name = "@alchemy.run/sigil";
565
- var version = "0.0.0-alpha.5";
567
+ var version = "0.0.0-alpha.6";
566
568
  //#endregion
567
569
  //#region src/reconciler.ts
568
570
  if (isSigilDev) await import("./devtools-DbthxoD1.js").catch(() => {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alchemy.run/sigil",
3
- "version": "0.0.0-alpha.5",
3
+ "version": "0.0.0-alpha.6",
4
4
  "description": "React for CLIs. A self-contained fork of Ink with an integrated TypeScript Yoga layout engine.",
5
5
  "keywords": [
6
6
  "cli",
@@ -59,7 +59,7 @@ export type Props = {
59
59
  readonly inverse?: boolean;
60
60
 
61
61
  /**
62
- This property tells Ink to wrap or truncate text if its width is larger than the container. If `wrap` is passed (the default), Ink will wrap text and split it into multiple lines. If `hard` is passed, Ink will fill each line to the full column width, breaking words as necessary. If `truncate-*` is passed, Ink will truncate text instead, resulting in one line of text with the rest cut off.
62
+ This property tells Ink to wrap or truncate text if its width is larger than the container. If `wrap` is passed (the default), Ink will wrap text and split it into multiple lines. If `hard` is passed, Ink will fill each line to the full column width, breaking words as necessary. If `truncate-*` is passed, Ink will truncate text instead, resulting in one line of text with the rest cut off. If `none` is passed, the text is neither wrapped nor truncated — it overflows the container (and the screen's nominal width), which is intended for log lines committed to scrollback (e.g. inside `<Static>`), not for live regions.
63
63
  */
64
64
  readonly wrap?: Styles["textWrap"];
65
65
 
package/src/dom.ts CHANGED
@@ -243,6 +243,12 @@ const measureTextNode = function (node: DOMNode, width: number): { width: number
243
243
  }
244
244
 
245
245
  const textWrap = node.style?.textWrap ?? "wrap";
246
+
247
+ // `none` keeps the intrinsic dimensions and overflows the container.
248
+ if (textWrap === "none") {
249
+ return dimensions;
250
+ }
251
+
246
252
  const wrappedText = wrapText(text, width, textWrap);
247
253
 
248
254
  return measureText(wrappedText);
package/src/paint-tree.ts CHANGED
@@ -117,10 +117,14 @@ export const paintTree = (
117
117
  const firstChildYoga = node.childNodes[0]?.yogaNode;
118
118
  const paddingX = firstChildYoga?.getComputedLeft() ?? 0;
119
119
  const paddingY = firstChildYoga?.getComputedTop() ?? 0;
120
+ const textWrap = node.style.textWrap ?? "wrap";
121
+ // `none` text may paint past the screen's nominal width; the screen
122
+ // grows the touched rows on demand. Clip rects still apply.
123
+ const overflow = textWrap === "none";
120
124
  const lines = wrapStructuredText(
121
125
  structuredTextLines(node),
122
126
  maxWidth,
123
- node.style.textWrap ?? "wrap",
127
+ textWrap,
124
128
  structuredTextBaseStyle(node),
125
129
  );
126
130
  const paintBounds = {
@@ -147,10 +151,10 @@ export const paintTree = (
147
151
  }),
148
152
  )
149
153
  .join("\n"),
150
- { transformers: textTransformers },
154
+ { transformers: textTransformers, overflow },
151
155
  );
152
156
  } else {
153
- output.writeCells(paintBounds.x, paintBounds.y, sampled);
157
+ output.writeCells(paintBounds.x, paintBounds.y, sampled, { overflow });
154
158
  }
155
159
  }
156
160
 
@@ -34,8 +34,14 @@ export class Canvas {
34
34
  this.#screen = new Screen(this.width, this.height);
35
35
  }
36
36
 
37
- writeCells(x: number, y: number, lines: readonly (readonly Cell[])[]): void {
37
+ writeCells(
38
+ x: number,
39
+ y: number,
40
+ lines: readonly (readonly Cell[])[],
41
+ options?: { overflow?: boolean },
42
+ ): void {
38
43
  const clip = this.#clips.at(-1);
44
+ const overflow = options?.overflow === true;
39
45
  for (const [rowOffset, line] of lines.entries()) {
40
46
  const currentY = y + rowOffset;
41
47
  if (!this.#insideY(currentY, clip)) continue;
@@ -45,7 +51,7 @@ export class Canvas {
45
51
  const clipped =
46
52
  (clip?.x1 !== undefined && currentX < clip.x1) ||
47
53
  (clip?.x2 !== undefined && endX > clip.x2);
48
- if (!clipped) this.#composeNativeCell(currentX, currentY, cell);
54
+ if (!clipped) this.#composeNativeCell(currentX, currentY, cell, overflow);
49
55
  currentX = endX;
50
56
  }
51
57
  }
@@ -55,11 +61,12 @@ export class Canvas {
55
61
  x: number,
56
62
  y: number,
57
63
  text: string,
58
- options: { transformers: AnsiTransformer[] },
64
+ options: { transformers: AnsiTransformer[]; overflow?: boolean },
59
65
  ): void {
60
66
  if (!text) return;
61
67
  let lines = text.split("\n");
62
68
  const clip = this.#clips.at(-1);
69
+ const overflow = options.overflow === true;
63
70
  if (clip?.y1 !== undefined && y < clip.y1) {
64
71
  lines = lines.slice(clip.y1 - y);
65
72
  y = clip.y1;
@@ -78,7 +85,7 @@ export class Canvas {
78
85
  for (const character of transformAnsiLine(line, lineIndex, options.transformers)) {
79
86
  const width = Math.max(1, stringWidth(character.value));
80
87
  if (clip?.x2 !== undefined && currentX + width > clip.x2) break;
81
- this.#writeCompatibilityCell(currentX, currentY, character, width);
88
+ this.#writeCompatibilityCell(currentX, currentY, character, width, overflow);
82
89
  currentX += width;
83
90
  }
84
91
  }
@@ -104,34 +111,50 @@ export class Canvas {
104
111
  );
105
112
  }
106
113
 
107
- #composeNativeCell(x: number, y: number, cell: Cell): void {
108
- if (x >= this.#screen.width) {
114
+ #composeNativeCell(x: number, y: number, cell: Cell, overflow = false): void {
115
+ if (!overflow && x >= this.#screen.width) {
109
116
  return;
110
117
  }
111
- this.#screen.composeCell(x, y, {
112
- content: { grapheme: cell.grapheme, width: cell.width },
113
- foreground: cell.reset?.foreground ? null : (cell.style.foreground ?? null),
114
- background: cell.reset?.background ? null : cell.style.background,
115
- underlineColor: cell.style.underlineColor ?? null,
116
- underline: cell.style.underline,
117
- attributes: cell.style.attributes,
118
- hyperlink: cell.hyperlink ?? null,
119
- });
118
+ this.#screen.composeCell(
119
+ x,
120
+ y,
121
+ {
122
+ content: { grapheme: cell.grapheme, width: cell.width },
123
+ foreground: cell.reset?.foreground ? null : (cell.style.foreground ?? null),
124
+ background: cell.reset?.background ? null : cell.style.background,
125
+ underlineColor: cell.style.underlineColor ?? null,
126
+ underline: cell.style.underline,
127
+ attributes: cell.style.attributes,
128
+ hyperlink: cell.hyperlink ?? null,
129
+ },
130
+ { overflow },
131
+ );
120
132
  }
121
133
 
122
- #writeCompatibilityCell(x: number, y: number, character: StyledChar, width: number): void {
123
- if (x >= this.#screen.width) {
134
+ #writeCompatibilityCell(
135
+ x: number,
136
+ y: number,
137
+ character: StyledChar,
138
+ width: number,
139
+ overflow = false,
140
+ ): void {
141
+ if (!overflow && x >= this.#screen.width) {
124
142
  return;
125
143
  }
126
144
  const cell = cellFromStyledChar(character, width);
127
- this.#screen.composeCell(x, y, {
128
- content: { grapheme: cell.grapheme, width: cell.width },
129
- foreground: cell.style.foreground ?? null,
130
- background: cell.style.background,
131
- underlineColor: cell.style.underlineColor ?? null,
132
- underline: cell.style.underline,
133
- attributes: cell.style.attributes,
134
- hyperlink: cell.hyperlink ?? null,
135
- });
145
+ this.#screen.composeCell(
146
+ x,
147
+ y,
148
+ {
149
+ content: { grapheme: cell.grapheme, width: cell.width },
150
+ foreground: cell.style.foreground ?? null,
151
+ background: cell.style.background,
152
+ underlineColor: cell.style.underlineColor ?? null,
153
+ underline: cell.style.underline,
154
+ attributes: cell.style.attributes,
155
+ hyperlink: cell.hyperlink ?? null,
156
+ },
157
+ { overflow },
158
+ );
136
159
  }
137
160
  }
@@ -39,6 +39,14 @@ export class Screen {
39
39
  return this.#width;
40
40
  }
41
41
 
42
+ /**
43
+ The populated length of one row: the nominal width, or more when overflow
44
+ writes (`textWrap: "none"` content) extended it past the screen's width.
45
+ */
46
+ rowLength(y: number): number {
47
+ return Math.max(this.#width, this.#rows[y]?.length ?? 0);
48
+ }
49
+
42
50
  get height(): number {
43
51
  return this.#height;
44
52
  }
@@ -56,9 +64,10 @@ export class Screen {
56
64
  return this.#painted[y]?.[x] ?? false;
57
65
  }
58
66
 
59
- setCell(x: number, y: number, cell: Cell): void {
67
+ setCell(x: number, y: number, cell: Cell, options?: { overflow?: boolean }): void {
60
68
  const row = this.#rows[y];
61
- if (!row || x < 0 || x >= this.width) {
69
+ const overflow = options?.overflow === true;
70
+ if (!row || x < 0 || (!overflow && x >= this.width)) {
62
71
  return;
63
72
  }
64
73
 
@@ -67,18 +76,23 @@ export class Screen {
67
76
  }
68
77
 
69
78
  const finalColumn = x + cell.width;
70
- const clipped = finalColumn > this.width;
71
- const overwriteEnd = Math.min(finalColumn, this.width);
79
+ if (overflow && finalColumn > row.length) {
80
+ this.#growRow(row, y, finalColumn);
81
+ }
82
+
83
+ const capacity = overflow ? row.length : this.width;
84
+ const clipped = finalColumn > capacity;
85
+ const overwriteEnd = Math.min(finalColumn, capacity);
72
86
 
73
87
  for (let column = x; column < overwriteEnd; column++) {
74
88
  this.#clearGraphemeAt(row, y, column);
75
89
  }
76
90
 
77
91
  if (clipped) {
78
- for (let column = x; column < this.width; column++) {
92
+ for (let column = x; column < capacity; column++) {
79
93
  row[column] = emptyCell;
80
94
  }
81
- this.#markDirty(y, x, this.width);
95
+ this.#markDirty(y, x, capacity);
82
96
 
83
97
  return;
84
98
  }
@@ -102,13 +116,18 @@ export class Screen {
102
116
  Composes independent cell channels over the existing cell. An undefined
103
117
  patch is transparent; an explicit space in `content` paints a blank.
104
118
  */
105
- composeCell(x: number, y: number, patch: CellPatch | undefined): void {
119
+ composeCell(
120
+ x: number,
121
+ y: number,
122
+ patch: CellPatch | undefined,
123
+ options?: { overflow?: boolean },
124
+ ): void {
106
125
  if (!patch) {
107
126
  return;
108
127
  }
109
128
 
110
129
  const row = this.#rows[y];
111
- if (!row || x < 0 || x >= this.width) {
130
+ if (!row || x < 0 || (options?.overflow !== true && x >= this.width)) {
112
131
  return;
113
132
  }
114
133
 
@@ -130,7 +149,12 @@ export class Screen {
130
149
  const hyperlink =
131
150
  patch.hyperlink === undefined ? destination.hyperlink : (patch.hyperlink ?? undefined);
132
151
 
133
- this.setCell(writeColumn, y, createCell(content.grapheme, content.width, style, hyperlink));
152
+ this.setCell(
153
+ writeColumn,
154
+ y,
155
+ createCell(content.grapheme, content.width, style, hyperlink),
156
+ options,
157
+ );
134
158
  }
135
159
 
136
160
  /** Composes a patch throughout a rectangle, clipped to this screen. */
@@ -191,7 +215,7 @@ export class Screen {
191
215
  for (const [y, row] of this.#rows.entries()) {
192
216
  row.fill(emptyCell);
193
217
  this.#painted[y]!.fill(true);
194
- this.#markDirty(y, 0, this.width);
218
+ this.#markDirty(y, 0, this.rowLength(y));
195
219
  }
196
220
  }
197
221
 
@@ -209,6 +233,15 @@ export class Screen {
209
233
  return spans;
210
234
  }
211
235
 
236
+ /** Extends a row (and its paint mask) so overflow writes land past `width`. */
237
+ #growRow(row: Cell[], y: number, length: number): void {
238
+ const painted = this.#painted[y]!;
239
+ while (row.length < length) {
240
+ row.push(emptyCell);
241
+ painted.push(false);
242
+ }
243
+ }
244
+
212
245
  #clearGraphemeAt(row: Cell[], y: number, x: number): void {
213
246
  const existing = row[x];
214
247
  if (!existing || existing === emptyCell) {
@@ -224,7 +257,7 @@ export class Screen {
224
257
  const width = Math.max(1, leadingCell?.width ?? 1);
225
258
  for (
226
259
  let column = leadingColumn;
227
- column < Math.min(leadingColumn + width, this.width);
260
+ column < Math.min(leadingColumn + width, row.length);
228
261
  column++
229
262
  ) {
230
263
  row[column] = emptyCell;
@@ -257,7 +290,7 @@ export class Screen {
257
290
  }
258
291
 
259
292
  const clippedStart = Math.max(0, start);
260
- const clippedEnd = Math.min(this.width, end);
293
+ const clippedEnd = Math.min(this.rowLength(y), end);
261
294
  const current = this.#dirty[y];
262
295
  this.#dirty[y] = current
263
296
  ? { start: Math.min(current.start, clippedStart), end: Math.max(current.end, clippedEnd) }
@@ -81,6 +81,10 @@ export function wrapStructuredText(
81
81
  wrap: Styles["textWrap"],
82
82
  baseStyle?: CellStyle,
83
83
  ): readonly (readonly Cell[])[] {
84
+ if (wrap === "none") {
85
+ return input.map((line) => [...line]);
86
+ }
87
+
84
88
  if (maxWidth < 1) {
85
89
  return [[]];
86
90
  }
package/src/styles.ts CHANGED
@@ -5,6 +5,11 @@ import { Yoga, type Node as YogaNode, type PositionType } from "#/yoga/index.ts"
5
5
  export type Styles = {
6
6
  /*
7
7
  We keep this as a single enum so overflow is one complete choice and invalid combinations like wrap + truncate-middle are unrepresentable. In hindsight, `normal` would have been a clearer default value than `wrap`, since it describes the standard behavior instead of repeating the prop name.
8
+
9
+ `none` neither wraps nor truncates: the text overflows the container (and
10
+ the screen's nominal width). Meant for scrollback content (`<Static>`),
11
+ not live regions, where a physically soft-wrapped line breaks the
12
+ presenter's line accounting.
8
13
  */
9
14
  readonly textWrap?:
10
15
  | "wrap"
@@ -12,7 +17,8 @@ export type Styles = {
12
17
  | "truncate-end"
13
18
  | "truncate"
14
19
  | "truncate-middle"
15
- | "truncate-start";
20
+ | "truncate-start"
21
+ | "none";
16
22
 
17
23
  /**
18
24
  Controls how the element is positioned.
@@ -171,7 +171,9 @@ function findFirstChangedRow(previous: Screen, next: Screen): number | undefined
171
171
  }
172
172
 
173
173
  function rowsEqual(left: Screen, right: Screen, y: number): boolean {
174
- for (let x = 0; x < left.width; x++) {
174
+ // Rows can extend past the nominal width when overflow content painted.
175
+ const length = Math.max(left.rowLength(y), right.rowLength(y));
176
+ for (let x = 0; x < length; x++) {
175
177
  if (!cellsEqual(left.cellAt(x, y), right.cellAt(x, y))) return false;
176
178
  }
177
179
  return true;
@@ -180,9 +182,7 @@ function rowsEqual(left: Screen, right: Screen, y: number): boolean {
180
182
  function screensEqual(left: Screen, right: Screen): boolean {
181
183
  if (left.width !== right.width || left.height !== right.height) return false;
182
184
  for (let y = 0; y < left.height; y++) {
183
- for (let x = 0; x < left.width; x++) {
184
- if (!cellsEqual(left.cellAt(x, y), right.cellAt(x, y))) return false;
185
- }
185
+ if (!rowsEqual(left, right, y)) return false;
186
186
  }
187
187
  return true;
188
188
  }
package/src/wrap-text.ts CHANGED
@@ -6,6 +6,10 @@ import { type Styles } from "#/styles.ts";
6
6
  export const wrapTextCache = new QuickLRU<string, string>({ maxSize: 4096 });
7
7
 
8
8
  export const wrapText = (text: string, maxWidth: number, wrapType: Styles["textWrap"]): string => {
9
+ if (wrapType === "none") {
10
+ return text;
11
+ }
12
+
9
13
  const cacheKey = text + String(maxWidth) + String(wrapType);
10
14
  const cachedText = wrapTextCache.get(cacheKey);
11
15