@flowtty/core 1.0.0-alpha.12 → 1.0.0-alpha.13

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.
@@ -45,6 +45,17 @@ interface Key {
45
45
  //#endregion
46
46
  //#region src/backend.d.ts
47
47
  interface Backend {
48
+ /**
49
+ * The render area, in cells.
50
+ *
51
+ * `height` may be `Infinity`: an unbounded surface — a report printed once
52
+ * rather than a screen to fit into. The renderer then lays the tree out with
53
+ * no height constraint (percent heights have nothing to resolve against and
54
+ * fall back to auto) and draws a buffer exactly as tall as the content, so a
55
+ * frame can be any number of rows. `useTerminalSize()` reports `Infinity`
56
+ * for the height too, which is what a component should branch on when it
57
+ * would otherwise size itself to the screen.
58
+ */
48
59
  size(): {
49
60
  width: number;
50
61
  height: number;
@@ -1,6 +1,6 @@
1
- import { D as getYoga, E as YogaNode, T as Yoga, _ as computeLayout, c as applyProps, d as insertBefore, f as measureText, g as Rect, h as removeChild, i as Instance, l as createInstance, m as refreshMeasure, n as Container, o as TextInstance, p as ownText, r as HostType, s as appendChild, u as createTextInstance, v as layoutOf, y as BORDER_CHARS } from "../host-C98WW3Ai.js";
1
+ import { D as Yoga, O as YogaNode, _ as Rect, b as layoutOf, c as appendChild, d as createTextInstance, f as insertBefore, g as removeChild, h as refreshMeasure, i as Instance, k as getYoga, l as applyProps, m as ownText, n as Container, o as TextInstance, p as measureText, r as HostType, u as createInstance, v as computeLayout, x as BORDER_CHARS, y as contentHeight } from "../host-DnKJiaQh.js";
2
2
  import { t as Buffer } from "../cells-C-GthybI.js";
3
3
  //#region src/host/paint.d.ts
4
4
  export declare function paint(container: Container, width: number, height: number): Buffer;
5
5
  //#endregion
6
- export { BORDER_CHARS, type Container, type HostType, type Instance, type Rect, type TextInstance, type Yoga, type YogaNode, appendChild, applyProps, computeLayout, createInstance, createTextInstance, getYoga, insertBefore, layoutOf, measureText, ownText, refreshMeasure, removeChild };
6
+ export { BORDER_CHARS, type Container, type HostType, type Instance, type Rect, type TextInstance, type Yoga, type YogaNode, appendChild, applyProps, computeLayout, contentHeight, createInstance, createTextInstance, getYoga, insertBefore, layoutOf, measureText, ownText, refreshMeasure, removeChild };
@@ -1,4 +1,4 @@
1
- import { a as Buffer, n as BORDER_CHARS, t as wrapText } from "../wrap-D5f0P1iA.js";
1
+ import { a as noteWarning, n as BORDER_CHARS, s as Buffer, t as wrapText } from "../wrap-DvO7jrLD.js";
2
2
  import { Align, Display, Edge, FlexDirection, Gutter, Justify, MeasureMode, PositionType, Wrap, loadYoga } from "yoga-layout/load";
3
3
  //#region src/host/yoga.ts
4
4
  let yogaPromise = null;
@@ -25,6 +25,7 @@ function createTextInstance(text, _Yoga) {
25
25
  };
26
26
  }
27
27
  function applyProps(inst, props, _Yoga) {
28
+ const hadRuns = inst.props?.runs !== void 0;
28
29
  inst.props = props;
29
30
  const n = inst.yogaNode;
30
31
  if (typeof props.width === "number") n.setWidth(props.width);
@@ -80,6 +81,7 @@ function applyProps(inst, props, _Yoga) {
80
81
  n.setAlignContent(acMap(props.alignContent));
81
82
  n.setJustifyContent(jcMap(props.justifyContent));
82
83
  n.setAlignItems(aiMap(props.alignItems));
84
+ if (hadRuns || props.runs !== void 0) refreshMeasure(inst, _Yoga);
83
85
  }
84
86
  function wrapMap(v) {
85
87
  switch (v) {
@@ -125,11 +127,13 @@ function measureText(text) {
125
127
  };
126
128
  }
127
129
  function ownText(inst) {
130
+ if (inst.props.runs !== void 0) return inst.props.runs.map((r) => r.text).join("");
128
131
  return inst.children.filter((c) => c.type === "text").map((c) => c.text).join("");
129
132
  }
130
133
  function refreshMeasure(inst, _Yoga) {
131
- const hasText = inst.children.some((c) => c.type === "text");
134
+ const hasText = inst.props.runs !== void 0 || inst.children.some((c) => c.type === "text");
132
135
  const hasBox = inst.children.some((c) => c.type === "box");
136
+ if (hasText && hasBox) noteWarning("flowtty: a box has both text and child boxes (e.g. <Text>a <Text bold>b</Text></Text>). <Text> is a box, not an inline span, so they do not flow as one line. Use <Span> for styled pieces inside a <Text>, or put separate <Text>s in a <Box flexDirection=\"row\">.");
133
137
  if (hasText && !hasBox) {
134
138
  const text = ownText(inst);
135
139
  const mode = inst.props.wrap ?? "none";
@@ -186,6 +190,15 @@ function insertBefore(parent, child, before, Yoga) {
186
190
  function computeLayout(container, width, height) {
187
191
  for (const root of container.children) root.yogaNode.calculateLayout(width, height);
188
192
  }
193
+ function contentHeight(container) {
194
+ let bottom = 0;
195
+ for (const root of container.children) {
196
+ const { top, height } = layoutOf(root);
197
+ const edge = top + height;
198
+ if (Number.isFinite(edge) && edge > bottom) bottom = edge;
199
+ }
200
+ return Math.ceil(bottom);
201
+ }
189
202
  function layoutOf(inst, offsetX = 0, offsetY = 0) {
190
203
  const n = inst.yogaNode;
191
204
  return {
@@ -215,6 +228,22 @@ function textStyleOf(inst) {
215
228
  if (p.backgroundColor !== void 0) style.bg = p.backgroundColor;
216
229
  return style;
217
230
  }
231
+ function runStyles(runs, base) {
232
+ const out = [];
233
+ for (const run of runs) {
234
+ const style = { ...base };
235
+ if (run.color !== void 0) style.fg = run.color;
236
+ if (run.backgroundColor !== void 0) style.bg = run.backgroundColor;
237
+ if (run.bold !== void 0) style.bold = run.bold;
238
+ if (run.dim !== void 0) style.dim = run.dim;
239
+ if (run.underline !== void 0) style.underline = run.underline;
240
+ if (run.inverse !== void 0) style.inverse = run.inverse;
241
+ if (run.strikethrough !== void 0) style.strikethrough = run.strikethrough;
242
+ if (run.link !== void 0) style.link = run.link;
243
+ for (const _ch of run.text) out.push(style);
244
+ }
245
+ return out;
246
+ }
218
247
  function setClipped(buffer, x, y, char, style, clip) {
219
248
  if (clip !== null) {
220
249
  if (x < clip.left || y < clip.top || x >= clip.left + clip.width || y >= clip.top + clip.height) return;
@@ -333,14 +362,25 @@ function paintInstance(inst, buffer, offsetX, offsetY, inheritedBg = void 0, cli
333
362
  const lines = mode === "none" ? text.split("\n") : wrapText(text, content.width, mode);
334
363
  const textStyle = textStyleOf(inst);
335
364
  if (textStyle.bg === void 0 && effectiveBg !== void 0) textStyle.bg = effectiveBg;
365
+ const styles = inst.props.runs !== void 0 ? runStyles(inst.props.runs, textStyle) : null;
366
+ const source = styles ? [...text] : null;
367
+ let at = 0;
336
368
  for (let row = 0; row < lines.length; row++) {
337
- if (row >= content.height) break;
338
369
  const chars = [...lines[row] ?? ""];
339
370
  for (let col = 0; col < chars.length; col++) {
340
- if (col >= content.width) break;
341
371
  const ch = chars[col];
372
+ let style = textStyle;
373
+ if (styles && source) {
374
+ let found = at;
375
+ while (found < source.length && source[found] !== ch) found++;
376
+ if (found < source.length) {
377
+ style = styles[found];
378
+ at = found + 1;
379
+ } else style = styles[Math.min(at, styles.length - 1)] ?? textStyle;
380
+ }
381
+ if (row >= content.height || col >= content.width) continue;
342
382
  const safe = ch.charCodeAt(0) < 32 ? " " : ch;
343
- setClipped(buffer, content.left + col, content.top + row, safe, textStyle, clip);
383
+ setClipped(buffer, content.left + col, content.top + row, safe, style, clip);
344
384
  }
345
385
  }
346
386
  }
@@ -380,4 +420,4 @@ function paintInstance(inst, buffer, offsetX, offsetY, inheritedBg = void 0, cli
380
420
  for (const child of absolutes) paintInstance(child, buffer, box.left, box.top, effectiveBg, overlayClip);
381
421
  }
382
422
  //#endregion
383
- export { BORDER_CHARS, appendChild, applyProps, computeLayout, createInstance, createTextInstance, getYoga, insertBefore, layoutOf, measureText, ownText, paint, refreshMeasure, removeChild };
423
+ export { BORDER_CHARS, appendChild, applyProps, computeLayout, contentHeight, createInstance, createTextInstance, getYoga, insertBefore, layoutOf, measureText, ownText, paint, refreshMeasure, removeChild };
@@ -35,7 +35,8 @@ interface Rect {
35
35
  width: number;
36
36
  height: number;
37
37
  }
38
- declare function computeLayout(container: Container, width: number, height: number): void;
38
+ declare function computeLayout(container: Container, width: number, height: number | undefined): void;
39
+ declare function contentHeight(container: Container): number;
39
40
  declare function layoutOf(inst: Instance, offsetX?: number, offsetY?: number): Rect;
40
41
  //#endregion
41
42
  //#region src/host/host.d.ts
@@ -55,6 +56,11 @@ interface BoxProps {
55
56
  justifyContent?: "flex-start" | "flex-end" | "center" | "space-between" | "space-around" | "space-evenly";
56
57
  /** Cross-axis alignment of children. */
57
58
  alignItems?: "flex-start" | "flex-end" | "center" | "stretch";
59
+ /** The box's text as styled pieces of ONE line of text: they are joined,
60
+ * measured and wrapped together as a single paragraph, and each piece paints
61
+ * with its own style on top of the box's text style. `<Text>` builds this from
62
+ * `<Span>` children. When set, it replaces the box's direct text children. */
63
+ runs?: TextRun[];
58
64
  wrap?: "wrap" | "truncate" | "none";
59
65
  color?: Color;
60
66
  bold?: boolean;
@@ -147,6 +153,18 @@ interface ScrollMetrics {
147
153
  scrollTop: number;
148
154
  maxScrollTop: number;
149
155
  }
156
+ /** One styled piece of a box's text — see `BoxProps.runs`. */
157
+ interface TextRun {
158
+ text: string;
159
+ color?: Color;
160
+ backgroundColor?: Color;
161
+ bold?: boolean;
162
+ dim?: boolean;
163
+ underline?: boolean;
164
+ inverse?: boolean;
165
+ strikethrough?: boolean;
166
+ link?: string;
167
+ }
150
168
  interface Instance {
151
169
  type: "box";
152
170
  props: BoxProps;
@@ -181,4 +199,4 @@ declare function appendChild(parent: Instance, child: Instance | TextInstance, Y
181
199
  declare function removeChild(parent: Instance, child: Instance | TextInstance, Yoga: Yoga): void;
182
200
  declare function insertBefore(parent: Instance, child: Instance | TextInstance, before: Instance | TextInstance, Yoga: Yoga): void;
183
201
  //#endregion
184
- export { GRID_CHARS as C, getYoga as D, YogaNode as E, DEFAULT_BORDER_STYLE as S, Yoga as T, computeLayout as _, ScrollMetrics as a, BorderChars as b, applyProps as c, insertBefore as d, measureText as f, Rect as g, removeChild as h, Instance as i, createInstance as l, refreshMeasure as m, Container as n, TextInstance as o, ownText as p, HostType as r, appendChild as s, BoxProps as t, createTextInstance as u, layoutOf as v, GridChars as w, BorderStyle as x, BORDER_CHARS as y };
202
+ export { BorderStyle as C, Yoga as D, GridChars as E, YogaNode as O, BorderChars as S, GRID_CHARS as T, Rect as _, ScrollMetrics as a, layoutOf as b, appendChild as c, createTextInstance as d, insertBefore as f, removeChild as g, refreshMeasure as h, Instance as i, getYoga as k, applyProps as l, ownText as m, Container as n, TextInstance as o, measureText as p, HostType as r, TextRun as s, BoxProps as t, createInstance as u, computeLayout as v, DEFAULT_BORDER_STYLE as w, BORDER_CHARS as x, contentHeight as y };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,10 @@
1
- import { C as GRID_CHARS, S as DEFAULT_BORDER_STYLE, a as ScrollMetrics, b as BorderChars, t as BoxProps, w as GridChars, x as BorderStyle } from "./host-C98WW3Ai.js";
1
+ import { C as BorderStyle, E as GridChars, S as BorderChars, T as GRID_CHARS, a as ScrollMetrics, s as TextRun, t as BoxProps, w as DEFAULT_BORDER_STYLE } from "./host-DnKJiaQh.js";
2
2
  import { a as NAMED_COLORS, i as Color, n as Cell, o as NamedColor, r as Style, t as Buffer } from "./cells-C-GthybI.js";
3
- import { a as NamedKey, i as NAMED_KEYS, n as Key, r as KeyName, t as Backend } from "./backend-CT8SGLO5.js";
3
+ import { a as NamedKey, i as NAMED_KEYS, n as Key, r as KeyName, t as Backend } from "./backend-DPFtzaxG.js";
4
+ //#region src/warnings.d.ts
5
+ /** Warnings noted since the last call; clears the list. */
6
+ export declare function takeWarnings(): string[];
7
+ //#endregion
4
8
  //#region src/wrap.d.ts
5
9
  type WrapMode = "wrap" | "truncate" | "none";
6
10
  /**
@@ -190,4 +194,4 @@ interface MultiSelectOptions {
190
194
  }
191
195
  declare function reduce$1<T>(items: SelectItem<T>[], state: MultiSelectState, key: Key, opts?: MultiSelectOptions): MultiSelectAction;
192
196
  //#endregion
193
- export { type Backend, type BorderChars, type BorderStyle, type BoxProps, Buffer, type Cell, type Color, DEFAULT_BORDER_STYLE, type EditorAction, type EditorOptions, type EditorState, GRID_CHARS, type GridChars, type InputRow, type Key, type KeyName, type MultiSelectAction, type MultiSelectOptions, type MultiSelectState, NAMED_COLORS, NAMED_KEYS, type NamedColor, type NamedKey, type ScrollMetrics, type SelectAction, type SelectItem, type SelectState, type Style, type VisualLine, type WrapMode, reduce as editorReducer, reduce$1 as multiSelectReducer, reduce$2 as selectReducer };
197
+ export { type Backend, type BorderChars, type BorderStyle, type BoxProps, Buffer, type Cell, type Color, DEFAULT_BORDER_STYLE, type EditorAction, type EditorOptions, type EditorState, GRID_CHARS, type GridChars, type InputRow, type Key, type KeyName, type MultiSelectAction, type MultiSelectOptions, type MultiSelectState, NAMED_COLORS, NAMED_KEYS, type NamedColor, type NamedKey, type ScrollMetrics, type SelectAction, type SelectItem, type SelectState, type Style, type TextRun, type VisualLine, type WrapMode, reduce as editorReducer, reduce$1 as multiSelectReducer, reduce$2 as selectReducer };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as Buffer, i as GRID_CHARS, r as DEFAULT_BORDER_STYLE, t as wrapText } from "./wrap-D5f0P1iA.js";
1
+ import { i as GRID_CHARS, o as takeWarnings, r as DEFAULT_BORDER_STYLE, s as Buffer, t as wrapText } from "./wrap-DvO7jrLD.js";
2
2
  import { t as NAMED_KEYS } from "./keys-Ck-RALk_.js";
3
3
  //#region src/visualLines.ts
4
4
  function splitVisualLines(text, mode, width) {
@@ -653,7 +653,7 @@ function reduce$2(items, state, key) {
653
653
  index: visible[Math.min(state.cursor, n - 1)]
654
654
  };
655
655
  }
656
- if (key.name === "down" || key.name === "j" && !key.ctrl && !key.meta) {
656
+ if (key.name === "down") {
657
657
  if (n === 0) return { kind: "noop" };
658
658
  return {
659
659
  kind: "state",
@@ -663,7 +663,7 @@ function reduce$2(items, state, key) {
663
663
  }
664
664
  };
665
665
  }
666
- if (key.name === "up" || key.name === "k" && !key.ctrl && !key.meta) {
666
+ if (key.name === "up") {
667
667
  if (n === 0) return { kind: "noop" };
668
668
  return {
669
669
  kind: "state",
@@ -718,4 +718,4 @@ function reduce$1(items, state, key, opts = {}) {
718
718
  return { kind: "noop" };
719
719
  }
720
720
  //#endregion
721
- export { Buffer, DEFAULT_BORDER_STYLE, GRID_CHARS, NAMED_COLORS, NAMED_KEYS, caretPosition, charWidth, reduce as editorReducer, inputRows, reduce$1 as multiSelectReducer, rowIndexAt, reduce$2 as selectReducer, splitVisualLines, stringWidth, visibleIndices, windowAround, wrapText };
721
+ export { Buffer, DEFAULT_BORDER_STYLE, GRID_CHARS, NAMED_COLORS, NAMED_KEYS, caretPosition, charWidth, reduce as editorReducer, inputRows, reduce$1 as multiSelectReducer, rowIndexAt, reduce$2 as selectReducer, splitVisualLines, stringWidth, takeWarnings, visibleIndices, windowAround, wrapText };
@@ -1,5 +1,5 @@
1
1
  import { t as Buffer } from "../cells-C-GthybI.js";
2
- import { n as Key, t as Backend } from "../backend-CT8SGLO5.js";
2
+ import { n as Key, t as Backend } from "../backend-DPFtzaxG.js";
3
3
  //#region src/testing/test-backend.d.ts
4
4
  export declare class TestBackend implements Backend {
5
5
  private readonly cols;
@@ -35,6 +35,18 @@ var Buffer = class {
35
35
  }
36
36
  };
37
37
  //#endregion
38
+ //#region src/warnings.ts
39
+ const pending = /* @__PURE__ */ new Set();
40
+ function noteWarning(message) {
41
+ pending.add(message);
42
+ }
43
+ /** Warnings noted since the last call; clears the list. */
44
+ function takeWarnings() {
45
+ const out = [...pending];
46
+ pending.clear();
47
+ return out;
48
+ }
49
+ //#endregion
38
50
  //#region src/host/borders.ts
39
51
  /** The library-wide default for components that draw a box-border chrome
40
52
  * (DialogHost wrappers, Menu panels, Table grids). Declared once so the
@@ -177,4 +189,4 @@ function wrapLine(line, width, out) {
177
189
  if (current) out.push(current);
178
190
  }
179
191
  //#endregion
180
- export { Buffer as a, GRID_CHARS as i, BORDER_CHARS as n, DEFAULT_BORDER_STYLE as r, wrapText as t };
192
+ export { noteWarning as a, GRID_CHARS as i, BORDER_CHARS as n, takeWarnings as o, DEFAULT_BORDER_STYLE as r, Buffer as s, wrapText as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowtty/core",
3
- "version": "1.0.0-alpha.12",
3
+ "version": "1.0.0-alpha.13",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {