@zakmandhro/bunti 0.1.4 → 0.1.5
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.
- package/README.md +66 -25
- package/dist/colors.d.ts +74 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +227 -0
- package/dist/colors.js.map +1 -0
- package/dist/components/Box.d.ts +5 -0
- package/dist/components/Box.d.ts.map +1 -0
- package/dist/components/Box.js +4 -0
- package/dist/components/Box.js.map +1 -0
- package/dist/components/Button.d.ts +14 -0
- package/dist/components/Button.d.ts.map +1 -0
- package/dist/components/Button.js +91 -0
- package/dist/components/Button.js.map +1 -0
- package/dist/components/Card.d.ts +11 -0
- package/dist/components/Card.d.ts.map +1 -0
- package/dist/components/Card.js +37 -0
- package/dist/components/Card.js.map +1 -0
- package/dist/components/Header.d.ts +13 -0
- package/dist/components/Header.d.ts.map +1 -0
- package/dist/components/Header.js +54 -0
- package/dist/components/Header.js.map +1 -0
- package/dist/components/Input.d.ts +16 -0
- package/dist/components/Input.d.ts.map +1 -0
- package/dist/components/Input.js +101 -0
- package/dist/components/Input.js.map +1 -0
- package/dist/components/Modal.d.ts +13 -0
- package/dist/components/Modal.d.ts.map +1 -0
- package/dist/components/Modal.js +28 -0
- package/dist/components/Modal.js.map +1 -0
- package/dist/components/index.d.ts +7 -0
- package/dist/components/index.d.ts.map +1 -0
- package/{src/components/index.ts → dist/components/index.js} +3 -0
- package/dist/components/index.js.map +1 -0
- package/dist/data/glyphs.d.ts +5 -0
- package/dist/data/glyphs.d.ts.map +1 -0
- package/dist/data/glyphs.js +29 -0
- package/dist/data/glyphs.js.map +1 -0
- package/dist/detect.d.ts +15 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +49 -0
- package/dist/detect.js.map +1 -0
- package/dist/dsl.d.ts +159 -0
- package/dist/dsl.d.ts.map +1 -0
- package/dist/dsl.js +663 -0
- package/dist/dsl.js.map +1 -0
- package/dist/easing.d.ts +5 -0
- package/dist/easing.d.ts.map +1 -0
- package/dist/easing.js +18 -0
- package/dist/easing.js.map +1 -0
- package/dist/geometry.d.ts +38 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +110 -0
- package/dist/geometry.js.map +1 -0
- package/dist/icons.d.ts +21 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +185 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +157 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +89 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +533 -0
- package/dist/layout.js.map +1 -0
- package/dist/render.d.ts +17 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +335 -0
- package/dist/render.js.map +1 -0
- package/dist/state.d.ts +92 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +105 -0
- package/dist/state.js.map +1 -0
- package/dist/utils.d.ts +27 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +157 -0
- package/dist/utils.js.map +1 -0
- package/package.json +33 -8
- package/src/colors.ts +0 -255
- package/src/components/Button.ts +0 -104
- package/src/components/Card.ts +0 -53
- package/src/components/Header.ts +0 -65
- package/src/components/Input.ts +0 -124
- package/src/data/glyphs.ts +0 -30
- package/src/detect.ts +0 -60
- package/src/dsl.ts +0 -661
- package/src/icons.ts +0 -186
- package/src/index.ts +0 -72
- package/src/layout.ts +0 -640
- package/src/render.ts +0 -302
- package/src/state.ts +0 -148
- package/src/utils.ts +0 -165
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,OAAO,KAAK,GAAG,MAAM,OAAO,CAAC;AAE7B,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AAEnC,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AAGjC,OAAO,EAAE,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEhF,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAErD,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,YAAY,EACV,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,eAAe,GAChB,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC9E,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,IAAI,EACJ,SAAS,EACT,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,WAAW,EACX,SAAS,GACV,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EACL,SAAS,EACT,QAAQ,EACR,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,YAAY,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,YAAY,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,KAAK,EACL,IAAI,EACJ,GAAG,EACH,WAAW,EACX,SAAS,EACT,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,KAAK,EACL,QAAQ,EACR,SAAS,GACV,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AACvC,YAAY,EAAE,IAAI,EAAE,GAAG,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,EACL,IAAI,EACJ,eAAe,EACf,iBAAiB,EACjB,YAAY,GACb,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,SAAS,EACT,WAAW,EACX,SAAS,EACT,QAAQ,EACR,YAAY,GACb,MAAM,SAAS,CAAC;AAGjB,eAAO,MAAM,KAAK;;;;WAcsR,CAAC;aAAe,CAAC;cAAgB,CAAC;YAAc,CAAC;;;;;;;;;YAAwy0B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0CAArqtB,YAAY;2CAA6O,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iDAAmoS,eAAe,EAAC,+BAA6B;;;;;;;;;;;;;;;iBAAw+J,CAAC;;;;gBAA/qd,CAAC;;;;;;;;;;CAH5sH,CAAC;AAEF,eAAe,KAAK,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as colors from './colors';
|
|
2
|
+
import * as detect from './detect';
|
|
3
|
+
import * as dsl from './dsl';
|
|
4
|
+
import * as easing from './easing';
|
|
5
|
+
import * as geometry from './geometry';
|
|
6
|
+
import * as icons from './icons';
|
|
7
|
+
import * as layout from './layout';
|
|
8
|
+
import * as render from './render';
|
|
9
|
+
import * as state from './state';
|
|
10
|
+
import * as utils from './utils';
|
|
11
|
+
export { bg, createGradient, fade, fg, hexToRGB, PALETTE, rgb } from './colors';
|
|
12
|
+
// Functional API Individual Exports
|
|
13
|
+
export { detectCapabilities } from './detect';
|
|
14
|
+
export { KEYS, render } from './dsl';
|
|
15
|
+
export { clamp01, easeInOutCubic, easeOutBack, easeOutCubic } from './easing';
|
|
16
|
+
export { innerRect, resolvePlacedRect, resolveRect, splitRect, } from './geometry';
|
|
17
|
+
export { EMOJI_MAP, ICON_MAP, icon, init, nerd, nerdIcon, register, replaceEmojis, } from './icons';
|
|
18
|
+
export { badge, blit, box, createStyle, getWindow, gradient, joinHorizontal, joinVertical, list, rect, setCell, table, viewport, wallpaper, } from './layout';
|
|
19
|
+
export { flush, loop } from './render';
|
|
20
|
+
export { ANSI, clearBackBuffer, createScreenState, resizeScreen, } from './state';
|
|
21
|
+
export { charWidth, indentBlock, stripAnsi, truncate, visibleWidth, } from './utils';
|
|
22
|
+
// Namespaced export for bunti.render style usage
|
|
23
|
+
export const bunti = {
|
|
24
|
+
...detect,
|
|
25
|
+
...icons,
|
|
26
|
+
...layout,
|
|
27
|
+
...render,
|
|
28
|
+
...utils,
|
|
29
|
+
...colors,
|
|
30
|
+
...state,
|
|
31
|
+
...dsl,
|
|
32
|
+
...easing,
|
|
33
|
+
...geometry,
|
|
34
|
+
};
|
|
35
|
+
export default bunti;
|
|
36
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,OAAO,KAAK,GAAG,MAAM,OAAO,CAAC;AAC7B,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AAEjC,OAAO,EAAE,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAGhF,oCAAoC;AACpC,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAQ9C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAQ9E,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,WAAW,EACX,SAAS,GACV,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,SAAS,EACT,QAAQ,EACR,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,aAAa,GACd,MAAM,SAAS,CAAC;AAOjB,OAAO,EACL,KAAK,EACL,IAAI,EACJ,GAAG,EACH,WAAW,EACX,SAAS,EACT,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,KAAK,EACL,QAAQ,EACR,SAAS,GACV,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAEvC,OAAO,EACL,IAAI,EACJ,eAAe,EACf,iBAAiB,EACjB,YAAY,GACb,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,SAAS,EACT,WAAW,EACX,SAAS,EACT,QAAQ,EACR,YAAY,GACb,MAAM,SAAS,CAAC;AAEjB,iDAAiD;AACjD,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,GAAG,MAAM;IACT,GAAG,KAAK;IACR,GAAG,MAAM;IACT,GAAG,MAAM;IACT,GAAG,KAAK;IACR,GAAG,MAAM;IACT,GAAG,KAAK;IACR,GAAG,GAAG;IACN,GAAG,MAAM;IACT,GAAG,QAAQ;CACZ,CAAC;AAEF,eAAe,KAAK,CAAC"}
|
package/dist/layout.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bunti Functional Layout & Drawing
|
|
3
|
+
* Strictly functional primitives for buffer manipulation and layout generation.
|
|
4
|
+
*/
|
|
5
|
+
import type { Cell, RGB, ScreenState } from './state';
|
|
6
|
+
export declare function setCell(state: ScreenState, x: number, y: number, cell: Partial<Cell>): void;
|
|
7
|
+
import type { Gradient } from './colors';
|
|
8
|
+
export interface RectOptions {
|
|
9
|
+
char?: string;
|
|
10
|
+
fg?: string | number | RGB;
|
|
11
|
+
bg?: string | number | RGB | Gradient;
|
|
12
|
+
}
|
|
13
|
+
export declare function rect(state: ScreenState, x: number, y: number, w: number, h: number, style: RectOptions): void;
|
|
14
|
+
export declare function blit(state: ScreenState, startX: number, startY: number, content: string, style?: Partial<Cell>): void;
|
|
15
|
+
export type BorderStyle = 'default' | 'rounded' | 'double' | 'dashed' | 'dotted' | 'frame' | 'thick-frame' | 'classic' | 'none';
|
|
16
|
+
export interface SideColors {
|
|
17
|
+
top?: any;
|
|
18
|
+
bottom?: any;
|
|
19
|
+
left?: any;
|
|
20
|
+
right?: any;
|
|
21
|
+
}
|
|
22
|
+
export type SizeUnit = number | string;
|
|
23
|
+
export interface StyleOptions {
|
|
24
|
+
width?: SizeUnit;
|
|
25
|
+
minWidth?: number;
|
|
26
|
+
maxWidth?: number;
|
|
27
|
+
height?: SizeUnit;
|
|
28
|
+
minHeight?: number;
|
|
29
|
+
maxHeight?: number;
|
|
30
|
+
padding?: [number, number];
|
|
31
|
+
border?: BorderStyle;
|
|
32
|
+
borderColor?: string | number | RGB | ((s: string) => string) | SideColors;
|
|
33
|
+
bgColor?: string | number | RGB | Gradient;
|
|
34
|
+
align?: 'left' | 'center' | 'right';
|
|
35
|
+
valign?: 'top' | 'middle' | 'bottom';
|
|
36
|
+
wrap?: boolean;
|
|
37
|
+
title?: string;
|
|
38
|
+
titleStyle?: (s: string) => string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolves a SizeUnit to an absolute integer based on parent dimensions.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveSize(unit: SizeUnit | undefined, parentDim: number, contentDim: number): number;
|
|
44
|
+
/**
|
|
45
|
+
* Generates a styled box string with perfect alignment and optional wrapping.
|
|
46
|
+
*/
|
|
47
|
+
export declare function box(content: string, options?: StyleOptions, parentW?: number, parentH?: number): string;
|
|
48
|
+
export declare function joinHorizontal(...blocks: string[]): string;
|
|
49
|
+
export declare function joinVertical(...blocks: string[]): string;
|
|
50
|
+
export declare function createStyle(defaults: StyleOptions): (content: string, overrides?: StyleOptions) => string;
|
|
51
|
+
export declare function badge(text: string, colorFn?: (s: string) => string): string;
|
|
52
|
+
export interface TableOptions {
|
|
53
|
+
width?: SizeUnit;
|
|
54
|
+
columns?: {
|
|
55
|
+
width?: SizeUnit;
|
|
56
|
+
align?: 'left' | 'center' | 'right';
|
|
57
|
+
}[];
|
|
58
|
+
border?: BorderStyle;
|
|
59
|
+
padding?: [number, number];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Renders a data table with perfectly aligned columns and shared borders.
|
|
63
|
+
*/
|
|
64
|
+
export declare function table(rows: string[][], options?: TableOptions, parentW?: number): string;
|
|
65
|
+
export declare function getWindow<T>(items: T[], selectedIndex: number, maxVisible: number): {
|
|
66
|
+
visible: T[];
|
|
67
|
+
start: number;
|
|
68
|
+
hasMoreAbove: boolean;
|
|
69
|
+
hasMoreBelow: boolean;
|
|
70
|
+
};
|
|
71
|
+
export interface ListOptions {
|
|
72
|
+
bullet?: string;
|
|
73
|
+
indent?: number;
|
|
74
|
+
focusedIndex?: number;
|
|
75
|
+
focusStyle?: (s: string) => string;
|
|
76
|
+
selectedBg?: string | number | RGB;
|
|
77
|
+
width?: SizeUnit;
|
|
78
|
+
maxVisible?: number;
|
|
79
|
+
interactive?: boolean;
|
|
80
|
+
}
|
|
81
|
+
export declare function list(items: string[], options?: ListOptions, parentW?: number): string;
|
|
82
|
+
export declare function viewport(content: string, width: number, height: number, scrollY?: number): string;
|
|
83
|
+
export declare function wallpaper(state: any, options: {
|
|
84
|
+
bg: any;
|
|
85
|
+
}): void;
|
|
86
|
+
export declare function gradient(state: any, colors: any[], options?: {
|
|
87
|
+
direction?: 'vertical' | 'horizontal';
|
|
88
|
+
}): void;
|
|
89
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"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"}
|
package/dist/layout.js
ADDED
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bunti Functional Layout & Drawing
|
|
3
|
+
* Strictly functional primitives for buffer manipulation and layout generation.
|
|
4
|
+
*/
|
|
5
|
+
import { resolveColor } from './colors';
|
|
6
|
+
import { replaceEmojis } from './icons';
|
|
7
|
+
import { charWidth, truncate, visibleWidth, wrapText } from './utils';
|
|
8
|
+
// --- Functional Primitives (Buffer Manipulation) ---
|
|
9
|
+
export function setCell(state, x, y, cell) {
|
|
10
|
+
if (x >= 0 && x < state.width && y >= 0 && y < state.height) {
|
|
11
|
+
const target = state.backBuffer[y * state.width + x];
|
|
12
|
+
let displayWidth = 1;
|
|
13
|
+
if (cell.char !== undefined) {
|
|
14
|
+
target.char = cell.raw ? cell.char : replaceEmojis(cell.char);
|
|
15
|
+
// Single-ASCII-char cells (fills, borders' spaces, plain text) skip width math.
|
|
16
|
+
displayWidth =
|
|
17
|
+
target.char.length === 1 && target.char.charCodeAt(0) < 0x80
|
|
18
|
+
? 1
|
|
19
|
+
: charWidth(target.char);
|
|
20
|
+
target.fg = undefined;
|
|
21
|
+
target.fgCode = undefined;
|
|
22
|
+
target.bold = false;
|
|
23
|
+
}
|
|
24
|
+
if (cell.fg !== undefined) {
|
|
25
|
+
target.fg = cell.fg;
|
|
26
|
+
target.fgCode = resolveColor(cell.fg);
|
|
27
|
+
}
|
|
28
|
+
if (cell.bg !== undefined) {
|
|
29
|
+
target.bg = cell.bg;
|
|
30
|
+
target.bgCode = resolveColor(cell.bg);
|
|
31
|
+
}
|
|
32
|
+
if (cell.bold !== undefined)
|
|
33
|
+
target.bold = cell.bold;
|
|
34
|
+
target.skip = cell.skip ?? false;
|
|
35
|
+
for (let dx = 1; dx < displayWidth; dx++) {
|
|
36
|
+
const skipX = x + dx;
|
|
37
|
+
if (skipX >= state.width)
|
|
38
|
+
break;
|
|
39
|
+
const skip = state.backBuffer[y * state.width + skipX];
|
|
40
|
+
skip.char = '';
|
|
41
|
+
skip.fg = target.fg;
|
|
42
|
+
skip.bg = target.bg;
|
|
43
|
+
skip.fgCode = target.fgCode;
|
|
44
|
+
skip.bgCode = target.bgCode;
|
|
45
|
+
skip.bold = target.bold;
|
|
46
|
+
skip.skip = true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function rect(state, x, y, w, h, style) {
|
|
51
|
+
const isGradient = style.bg && typeof style.bg === 'object' && 'colors' in style.bg;
|
|
52
|
+
for (let dy = 0; dy < h; dy++) {
|
|
53
|
+
const row = y + dy;
|
|
54
|
+
if (row < 0 || row >= state.height)
|
|
55
|
+
continue;
|
|
56
|
+
for (let dx = 0; dx < w; dx++) {
|
|
57
|
+
const col = x + dx;
|
|
58
|
+
if (col < 0 || col >= state.width)
|
|
59
|
+
continue;
|
|
60
|
+
let resolvedBg;
|
|
61
|
+
if (isGradient) {
|
|
62
|
+
const grad = style.bg;
|
|
63
|
+
if (grad.direction === 'horizontal') {
|
|
64
|
+
resolvedBg = grad.colors[Math.floor((dx / w) * grad.colors.length)];
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
resolvedBg = grad.colors[Math.floor((dy / h) * grad.colors.length)];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
resolvedBg =
|
|
72
|
+
style.bg !== undefined
|
|
73
|
+
? resolveColor(style.bg)
|
|
74
|
+
: undefined;
|
|
75
|
+
}
|
|
76
|
+
const cell = { bg: resolvedBg };
|
|
77
|
+
if (style.char)
|
|
78
|
+
cell.char = style.char;
|
|
79
|
+
if (style.fg)
|
|
80
|
+
cell.fg = resolveColor(style.fg);
|
|
81
|
+
const existing = state.backBuffer[row * state.width + col];
|
|
82
|
+
if (existing.char !== ' ' && !style.char) {
|
|
83
|
+
// Keep existing char/fg if drawing a pure background
|
|
84
|
+
cell.char = existing.char;
|
|
85
|
+
cell.fg = existing.fg;
|
|
86
|
+
}
|
|
87
|
+
setCell(state, col, row, cell);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export function blit(state, startX, startY, content, style = {}) {
|
|
92
|
+
const lines = content.split('\n');
|
|
93
|
+
for (let row = 0; row < lines.length; row++) {
|
|
94
|
+
const line = lines[row];
|
|
95
|
+
let x = startX;
|
|
96
|
+
let currentFg, currentBg;
|
|
97
|
+
let currentBold = false;
|
|
98
|
+
const regex = /\x1B\[([0-9;]*)m|([^\x1B]+)/g;
|
|
99
|
+
let match = null;
|
|
100
|
+
while ((match = regex.exec(line)) !== null) {
|
|
101
|
+
if (match[1] !== undefined) {
|
|
102
|
+
const codes = match[1].split(';');
|
|
103
|
+
for (let i = 0; i < codes.length; i++) {
|
|
104
|
+
const code = parseInt(codes[i] || '0', 10);
|
|
105
|
+
if (code === 0) {
|
|
106
|
+
currentFg = undefined;
|
|
107
|
+
currentBg = undefined;
|
|
108
|
+
currentBold = false;
|
|
109
|
+
}
|
|
110
|
+
else if (code === 1)
|
|
111
|
+
currentBold = true;
|
|
112
|
+
else if (code === 22)
|
|
113
|
+
currentBold = false;
|
|
114
|
+
else if (code >= 30 && code <= 37)
|
|
115
|
+
currentFg = (code - 30).toString();
|
|
116
|
+
else if (code >= 40 && code <= 47)
|
|
117
|
+
currentBg = (code - 40).toString();
|
|
118
|
+
else if (code >= 90 && code <= 97)
|
|
119
|
+
currentFg = (code - 90 + 8).toString();
|
|
120
|
+
else if (code >= 100 && code <= 107)
|
|
121
|
+
currentBg = (code - 100 + 8).toString();
|
|
122
|
+
else if (code === 38 && codes[i + 1] === '5') {
|
|
123
|
+
currentFg = parseInt(codes[i + 2], 10);
|
|
124
|
+
i += 2;
|
|
125
|
+
}
|
|
126
|
+
else if (code === 38 && codes[i + 1] === '2') {
|
|
127
|
+
currentFg = {
|
|
128
|
+
r: parseInt(codes[i + 2], 10),
|
|
129
|
+
g: parseInt(codes[i + 3], 10),
|
|
130
|
+
b: parseInt(codes[i + 4], 10),
|
|
131
|
+
};
|
|
132
|
+
i += 4;
|
|
133
|
+
}
|
|
134
|
+
else if (code === 48 && codes[i + 1] === '5') {
|
|
135
|
+
currentBg = parseInt(codes[i + 2], 10);
|
|
136
|
+
i += 2;
|
|
137
|
+
}
|
|
138
|
+
else if (code === 48 && codes[i + 1] === '2') {
|
|
139
|
+
currentBg = {
|
|
140
|
+
r: parseInt(codes[i + 2], 10),
|
|
141
|
+
g: parseInt(codes[i + 3], 10),
|
|
142
|
+
b: parseInt(codes[i + 4], 10),
|
|
143
|
+
};
|
|
144
|
+
i += 4;
|
|
145
|
+
}
|
|
146
|
+
else if (code === 39)
|
|
147
|
+
currentFg = undefined;
|
|
148
|
+
else if (code === 49)
|
|
149
|
+
currentBg = undefined;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else if (match[2]) {
|
|
153
|
+
const processedText = replaceEmojis(match[2]);
|
|
154
|
+
const segmenter = new Intl.Segmenter('en', {
|
|
155
|
+
granularity: 'grapheme',
|
|
156
|
+
});
|
|
157
|
+
for (const { segment } of segmenter.segment(processedText)) {
|
|
158
|
+
const w = charWidth(segment);
|
|
159
|
+
if (w === 0)
|
|
160
|
+
continue;
|
|
161
|
+
const cell = { char: segment, ...style };
|
|
162
|
+
// Only overwrite buffer colors if explicitly set in the string or style
|
|
163
|
+
if (currentFg !== undefined)
|
|
164
|
+
cell.fg = currentFg;
|
|
165
|
+
else if (style.fg !== undefined)
|
|
166
|
+
cell.fg = style.fg;
|
|
167
|
+
else if (state.options.defaultFg !== undefined)
|
|
168
|
+
cell.fg = state.options.defaultFg;
|
|
169
|
+
if (currentBg !== undefined)
|
|
170
|
+
cell.bg = currentBg;
|
|
171
|
+
else if (style.bg !== undefined)
|
|
172
|
+
cell.bg = style.bg;
|
|
173
|
+
if (currentBold)
|
|
174
|
+
cell.bold = true;
|
|
175
|
+
setCell(state, x, startY + row, cell);
|
|
176
|
+
x += w;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const BORDERS = {
|
|
183
|
+
default: { tl: '┌', tr: '┐', bl: '└', br: '┘', h: '─', v: '│' },
|
|
184
|
+
rounded: { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│' },
|
|
185
|
+
double: { tl: '╔', tr: '╗', bl: '╚', br: '╝', h: '═', v: '║' },
|
|
186
|
+
dashed: { tl: '┌', tr: '┐', bl: '└', br: '┘', h: '╌', v: '╎' },
|
|
187
|
+
dotted: { tl: '┌', tr: '┐', bl: '└', br: '┘', h: '┈', v: '┊' },
|
|
188
|
+
frame: {
|
|
189
|
+
tl: '█',
|
|
190
|
+
tr: '█',
|
|
191
|
+
bl: '█',
|
|
192
|
+
br: '█',
|
|
193
|
+
top: '▀',
|
|
194
|
+
bottom: '▄',
|
|
195
|
+
left: '█',
|
|
196
|
+
right: '█',
|
|
197
|
+
},
|
|
198
|
+
'thick-frame': { tl: '█', tr: '█', bl: '█', br: '█', h: '█', v: '█' },
|
|
199
|
+
classic: { tl: '+', tr: '+', bl: '+', br: '+', h: '-', v: '|' },
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* Resolves a SizeUnit to an absolute integer based on parent dimensions.
|
|
203
|
+
*/
|
|
204
|
+
export function resolveSize(unit, parentDim, contentDim) {
|
|
205
|
+
if (unit === undefined || unit === 'auto')
|
|
206
|
+
return contentDim;
|
|
207
|
+
if (typeof unit === 'number')
|
|
208
|
+
return unit;
|
|
209
|
+
if (typeof unit === 'string') {
|
|
210
|
+
if (unit.endsWith('%')) {
|
|
211
|
+
const pct = parseFloat(unit) / 100;
|
|
212
|
+
return Math.floor(parentDim * pct);
|
|
213
|
+
}
|
|
214
|
+
if (unit.endsWith('fr')) {
|
|
215
|
+
return parentDim;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return contentDim;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Generates a styled box string with perfect alignment and optional wrapping.
|
|
222
|
+
*/
|
|
223
|
+
export function box(content, options = {}, parentW, parentH) {
|
|
224
|
+
// Ensure we operate on the swapped glyphs for all layout math
|
|
225
|
+
content = replaceEmojis(content);
|
|
226
|
+
const px = options.padding?.[1] ?? 0;
|
|
227
|
+
const py = options.padding?.[0] ?? 0;
|
|
228
|
+
const borderStyle = options.border || 'none';
|
|
229
|
+
const borderOffset = borderStyle === 'none' ? 0 : 2;
|
|
230
|
+
const rawLines = content.split('\n');
|
|
231
|
+
const maxRawW = Math.max(...rawLines.map((l) => visibleWidth(l)), 0);
|
|
232
|
+
const intrinsicW = maxRawW + px * 2 + borderOffset;
|
|
233
|
+
// 1. Resolve Target Width (Outer)
|
|
234
|
+
const resolvedW = resolveSize(options.width, parentW || 0, intrinsicW);
|
|
235
|
+
// 2. Calculate Inner Width (between borders, including padding)
|
|
236
|
+
let targetInnerW = Math.max(0, resolvedW - borderOffset);
|
|
237
|
+
if (options.minWidth)
|
|
238
|
+
targetInnerW = Math.max(targetInnerW, options.minWidth - borderOffset);
|
|
239
|
+
if (options.maxWidth)
|
|
240
|
+
targetInnerW = Math.min(targetInnerW, options.maxWidth - borderOffset);
|
|
241
|
+
let lines = [];
|
|
242
|
+
const contentWidth = Math.max(0, targetInnerW - px * 2);
|
|
243
|
+
if (options.wrap && contentWidth > 0) {
|
|
244
|
+
lines = wrapText(content, contentWidth);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
lines = content.split('\n').map((l) => truncate(l, contentWidth));
|
|
248
|
+
}
|
|
249
|
+
const contentH = lines.length + py * 2;
|
|
250
|
+
const resolvedH = resolveSize(options.height, parentH || 0, contentH);
|
|
251
|
+
let finalInnerH = resolvedH ? resolvedH - borderOffset : contentH;
|
|
252
|
+
if (options.minHeight)
|
|
253
|
+
finalInnerH = Math.max(finalInnerH, options.minHeight - borderOffset);
|
|
254
|
+
if (options.maxHeight)
|
|
255
|
+
finalInnerH = Math.min(finalInnerH, options.maxHeight - borderOffset);
|
|
256
|
+
const b = borderStyle === 'none'
|
|
257
|
+
? null
|
|
258
|
+
: BORDERS[borderStyle] || BORDERS.default;
|
|
259
|
+
// Color Resolution
|
|
260
|
+
const { fg } = require('./colors');
|
|
261
|
+
const resolveSide = (color) => typeof color === 'function' ? color : (s) => fg(color, s);
|
|
262
|
+
const wrapBg = (s) => {
|
|
263
|
+
if (!options.bgColor)
|
|
264
|
+
return s;
|
|
265
|
+
if (typeof options.bgColor === 'object' && 'colors' in options.bgColor) {
|
|
266
|
+
// Gradients cannot be wrapped as a single ANSI string because they change per-cell.
|
|
267
|
+
// In string-building mode, we must ignore gradients. They are handled in absolute rect mode.
|
|
268
|
+
return s;
|
|
269
|
+
}
|
|
270
|
+
const { resolveColor } = require('./colors');
|
|
271
|
+
const code = resolveColor(options.bgColor);
|
|
272
|
+
const prefix = typeof options.bgColor === 'object' || String(code).startsWith('2;')
|
|
273
|
+
? '48'
|
|
274
|
+
: '48;5';
|
|
275
|
+
const bgOn = `\x1b[${prefix};${code}m`;
|
|
276
|
+
// Apply bg, and if the string contains resets, re-apply the bg right after the reset.
|
|
277
|
+
// We add a final reset at the very end to clean up.
|
|
278
|
+
return `${bgOn + s.replace(/\x1b\[0m/g, `\x1b[0m${bgOn}`)}\x1b[0m`;
|
|
279
|
+
};
|
|
280
|
+
const bc = options.borderColor;
|
|
281
|
+
const colors = typeof bc === 'object' && !('r' in bc)
|
|
282
|
+
? {
|
|
283
|
+
top: resolveSide(bc.top ||
|
|
284
|
+
bc.left ||
|
|
285
|
+
bc.right),
|
|
286
|
+
bottom: resolveSide(bc.bottom ||
|
|
287
|
+
bc.left ||
|
|
288
|
+
bc.right),
|
|
289
|
+
left: resolveSide(bc.left || bc.top),
|
|
290
|
+
right: resolveSide(bc.right || bc.top),
|
|
291
|
+
}
|
|
292
|
+
: {
|
|
293
|
+
top: resolveSide(bc || ((s) => s)),
|
|
294
|
+
bottom: resolveSide(bc || ((s) => s)),
|
|
295
|
+
left: resolveSide(bc || ((s) => s)),
|
|
296
|
+
right: resolveSide(bc || ((s) => s)),
|
|
297
|
+
};
|
|
298
|
+
const hTop = b?.top || b?.h || ' ';
|
|
299
|
+
const hBottom = b?.bottom || b?.h || ' ';
|
|
300
|
+
const vLeft = b?.left || b?.v || ' ';
|
|
301
|
+
const vRight = b?.right || b?.v || ' ';
|
|
302
|
+
const vSpace = Math.max(0, finalInnerH - lines.length - py * 2);
|
|
303
|
+
let topS = 0, bottomS = vSpace;
|
|
304
|
+
if (options.valign === 'middle') {
|
|
305
|
+
topS = Math.floor(vSpace / 2);
|
|
306
|
+
bottomS = Math.ceil(vSpace / 2);
|
|
307
|
+
}
|
|
308
|
+
else if (options.valign === 'bottom') {
|
|
309
|
+
topS = vSpace;
|
|
310
|
+
bottomS = 0;
|
|
311
|
+
}
|
|
312
|
+
const out = [];
|
|
313
|
+
// Top Border
|
|
314
|
+
if (b) {
|
|
315
|
+
const title = options.title && targetInnerW > 2
|
|
316
|
+
? truncate(` ${options.title.trim()} `, targetInnerW)
|
|
317
|
+
: '';
|
|
318
|
+
const styledTitle = options.titleStyle ? options.titleStyle(title) : title;
|
|
319
|
+
const titleW = visibleWidth(title);
|
|
320
|
+
const beforeTitle = title ? 1 : 0;
|
|
321
|
+
const afterTitle = Math.max(0, targetInnerW - beforeTitle - titleW);
|
|
322
|
+
out.push(wrapBg(colors.top(b.tl) +
|
|
323
|
+
colors.top(hTop.repeat(beforeTitle)) +
|
|
324
|
+
styledTitle +
|
|
325
|
+
colors.top(hTop.repeat(afterTitle)) +
|
|
326
|
+
colors.top(b.tr)));
|
|
327
|
+
}
|
|
328
|
+
// Top Padding
|
|
329
|
+
for (let i = 0; i < py + topS; i++) {
|
|
330
|
+
let row = b ? colors.left(vLeft) : '';
|
|
331
|
+
row += wrapBg(' '.repeat(targetInnerW));
|
|
332
|
+
if (b)
|
|
333
|
+
row += colors.right(vRight);
|
|
334
|
+
out.push(row);
|
|
335
|
+
}
|
|
336
|
+
// Content Lines
|
|
337
|
+
for (let i = 0; i < lines.length; i++) {
|
|
338
|
+
const line = lines[i].trimEnd();
|
|
339
|
+
const lineW = visibleWidth(line);
|
|
340
|
+
const extra = Math.max(0, targetInnerW - lineW - px * 2);
|
|
341
|
+
let left = 0, right = 0;
|
|
342
|
+
const align = options.align || 'left';
|
|
343
|
+
if (align === 'center') {
|
|
344
|
+
left = px + Math.floor(extra / 2);
|
|
345
|
+
right = px + Math.ceil(extra / 2);
|
|
346
|
+
}
|
|
347
|
+
else if (align === 'right') {
|
|
348
|
+
left = px + extra;
|
|
349
|
+
right = px;
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
left = px;
|
|
353
|
+
right = px + extra;
|
|
354
|
+
}
|
|
355
|
+
let row = b ? colors.left(vLeft) : '';
|
|
356
|
+
row += wrapBg(' '.repeat(Math.max(0, left)) + line + ' '.repeat(Math.max(0, right)));
|
|
357
|
+
if (b)
|
|
358
|
+
row += colors.right(vRight);
|
|
359
|
+
out.push(row);
|
|
360
|
+
}
|
|
361
|
+
// Bottom Padding
|
|
362
|
+
for (let i = 0; i < py + bottomS; i++) {
|
|
363
|
+
let row = b ? colors.left(vLeft) : '';
|
|
364
|
+
row += wrapBg(' '.repeat(targetInnerW));
|
|
365
|
+
if (b)
|
|
366
|
+
row += colors.right(vRight);
|
|
367
|
+
out.push(row);
|
|
368
|
+
}
|
|
369
|
+
// Bottom Border
|
|
370
|
+
if (b) {
|
|
371
|
+
out.push(wrapBg(colors.bottom(b.bl) +
|
|
372
|
+
colors.bottom(hBottom.repeat(targetInnerW)) +
|
|
373
|
+
colors.bottom(b.br)));
|
|
374
|
+
}
|
|
375
|
+
return out.join('\n');
|
|
376
|
+
}
|
|
377
|
+
export function joinHorizontal(...blocks) {
|
|
378
|
+
const parsed = blocks.map((b) => b.split('\n'));
|
|
379
|
+
const maxH = Math.max(...parsed.map((p) => p.length));
|
|
380
|
+
const widths = parsed.map((p) => Math.max(...p.map(visibleWidth)));
|
|
381
|
+
const out = [];
|
|
382
|
+
for (let i = 0; i < maxH; i++) {
|
|
383
|
+
let row = '';
|
|
384
|
+
for (let j = 0; j < parsed.length; j++) {
|
|
385
|
+
const block = parsed[j];
|
|
386
|
+
const targetW = widths[j];
|
|
387
|
+
if (block[i] !== undefined) {
|
|
388
|
+
row +=
|
|
389
|
+
block[i] +
|
|
390
|
+
' '.repeat(Math.max(0, targetW - visibleWidth(block[i])));
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
row += ' '.repeat(targetW);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
out.push(row);
|
|
397
|
+
}
|
|
398
|
+
return out.join('\n');
|
|
399
|
+
}
|
|
400
|
+
export function joinVertical(...blocks) {
|
|
401
|
+
return blocks.filter(Boolean).join('\n');
|
|
402
|
+
}
|
|
403
|
+
export function createStyle(defaults) {
|
|
404
|
+
return (content, overrides = {}) => {
|
|
405
|
+
return box(content, { ...defaults, ...overrides });
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
export function badge(text, colorFn = (s) => s) {
|
|
409
|
+
return colorFn(` ${text.toUpperCase()} `);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Renders a data table with perfectly aligned columns and shared borders.
|
|
413
|
+
*/
|
|
414
|
+
export function table(rows, options = {}, parentW) {
|
|
415
|
+
const borderStyle = options.border || 'default';
|
|
416
|
+
const _px = options.padding?.[1] ?? 1;
|
|
417
|
+
const _py = options.padding?.[0] ?? 0;
|
|
418
|
+
const colCount = rows[0]?.length || 0;
|
|
419
|
+
if (colCount === 0)
|
|
420
|
+
return '';
|
|
421
|
+
// 1. Resolve Column Widths
|
|
422
|
+
const resolvedWidth = resolveSize(options.width, parentW || 0, 80);
|
|
423
|
+
const gutterW = 1;
|
|
424
|
+
const colWidth = Math.floor((resolvedWidth - (colCount - 1) * gutterW) / colCount);
|
|
425
|
+
// 2. Render each cell as a rigid block
|
|
426
|
+
const renderedRows = rows.map((row) => {
|
|
427
|
+
const cells = row.map((content, i) => {
|
|
428
|
+
// Explicitly pad empty content to ensure it occupies the full column width
|
|
429
|
+
const safeContent = content || ' '.repeat(colWidth);
|
|
430
|
+
const cellAlign = options.columns?.[i]?.align
|
|
431
|
+
? options.columns[i].align
|
|
432
|
+
: 'left';
|
|
433
|
+
return box(safeContent, {
|
|
434
|
+
width: colWidth,
|
|
435
|
+
border: 'none',
|
|
436
|
+
// Strict internal zero-padding; let the gutter handle spacing
|
|
437
|
+
padding: [0, 0],
|
|
438
|
+
align: cellAlign,
|
|
439
|
+
}, colWidth, 0);
|
|
440
|
+
});
|
|
441
|
+
// Join with gutter
|
|
442
|
+
return joinHorizontal(...cells.flatMap((c, i) => i < cells.length - 1 ? [c, ' '.repeat(gutterW)] : [c]));
|
|
443
|
+
});
|
|
444
|
+
return box(joinVertical(...renderedRows), {
|
|
445
|
+
border: borderStyle,
|
|
446
|
+
padding: [0, 0],
|
|
447
|
+
width: resolvedWidth,
|
|
448
|
+
align: 'left',
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
export function getWindow(items, selectedIndex, maxVisible) {
|
|
452
|
+
const start = Math.max(0, Math.min(selectedIndex - Math.floor(maxVisible / 2), items.length - maxVisible));
|
|
453
|
+
const visible = items.slice(start, start + maxVisible);
|
|
454
|
+
return {
|
|
455
|
+
visible,
|
|
456
|
+
start,
|
|
457
|
+
hasMoreAbove: start > 0,
|
|
458
|
+
hasMoreBelow: start + maxVisible < items.length,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
export function list(items, options = {}, parentW) {
|
|
462
|
+
const bullet = options.bullet || '';
|
|
463
|
+
const indent = ' '.repeat(options.indent || 0);
|
|
464
|
+
const resolvedW = resolveSize(options.width, parentW || 0, 0);
|
|
465
|
+
const { resolveColor } = require('./colors');
|
|
466
|
+
const wrapSelectedBg = (line) => {
|
|
467
|
+
if (!options.selectedBg)
|
|
468
|
+
return line;
|
|
469
|
+
const code = resolveColor(options.selectedBg);
|
|
470
|
+
const codeStr = String(code);
|
|
471
|
+
const prefix = typeof options.selectedBg === 'object' || codeStr.startsWith('2;')
|
|
472
|
+
? '48'
|
|
473
|
+
: '48;5';
|
|
474
|
+
const bgOn = `\x1b[${prefix};${codeStr}m`;
|
|
475
|
+
return `${bgOn + line.replace(/\x1b\[0m/g, `\x1b[0m${bgOn}`)}\x1b[0m`;
|
|
476
|
+
};
|
|
477
|
+
let targetItems = items, offset = 0, hasMoreAbove = false, hasMoreBelow = false;
|
|
478
|
+
if (options.maxVisible && items.length > options.maxVisible) {
|
|
479
|
+
const win = getWindow(items, options.focusedIndex || 0, options.maxVisible);
|
|
480
|
+
targetItems = win.visible;
|
|
481
|
+
offset = win.start;
|
|
482
|
+
hasMoreAbove = win.hasMoreAbove;
|
|
483
|
+
hasMoreBelow = win.hasMoreBelow;
|
|
484
|
+
}
|
|
485
|
+
const rendered = targetItems
|
|
486
|
+
.map((item, idx) => {
|
|
487
|
+
const actualIdx = offset + idx;
|
|
488
|
+
const line = `${indent}${bullet}${item}`;
|
|
489
|
+
if (options.focusedIndex === actualIdx) {
|
|
490
|
+
const styledLine = options.focusStyle ? options.focusStyle(line) : line;
|
|
491
|
+
if (!options.selectedBg)
|
|
492
|
+
return styledLine;
|
|
493
|
+
const paddedLine = resolvedW > 0
|
|
494
|
+
? styledLine +
|
|
495
|
+
' '.repeat(Math.max(0, resolvedW - visibleWidth(styledLine)))
|
|
496
|
+
: styledLine;
|
|
497
|
+
return wrapSelectedBg(paddedLine);
|
|
498
|
+
}
|
|
499
|
+
return line;
|
|
500
|
+
})
|
|
501
|
+
.join('\n');
|
|
502
|
+
let out = rendered;
|
|
503
|
+
if (hasMoreAbove)
|
|
504
|
+
out = `${indent} ↑ more…\n${out}`;
|
|
505
|
+
if (hasMoreBelow)
|
|
506
|
+
out = `${out}\n${indent} ↓ more…`;
|
|
507
|
+
return out;
|
|
508
|
+
}
|
|
509
|
+
export function viewport(content, width, height, scrollY = 0) {
|
|
510
|
+
const lines = content.split('\n');
|
|
511
|
+
const visibleLines = lines.slice(scrollY, scrollY + height);
|
|
512
|
+
return visibleLines
|
|
513
|
+
.map((line) => {
|
|
514
|
+
return truncate(line, width, '');
|
|
515
|
+
})
|
|
516
|
+
.join('\n');
|
|
517
|
+
}
|
|
518
|
+
export function wallpaper(state, options) {
|
|
519
|
+
const { bg } = options;
|
|
520
|
+
rect(state, 0, 0, state.width, state.height, { char: ' ', bg });
|
|
521
|
+
}
|
|
522
|
+
export function gradient(state, colors, options = {}) {
|
|
523
|
+
const { direction = 'vertical' } = options;
|
|
524
|
+
rect(state, 0, 0, state.width, state.height, {
|
|
525
|
+
char: ' ',
|
|
526
|
+
bg: {
|
|
527
|
+
colors,
|
|
528
|
+
direction,
|
|
529
|
+
steps: direction === 'vertical' ? state.height : state.width,
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
//# sourceMappingURL=layout.js.map
|