@markii/ansi 0.15.0 → 0.17.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/ansi.d.ts +26 -0
- package/dist/ansi.js +51 -0
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/badge.js +5 -4
- package/dist/components/callout.d.ts +38 -13
- package/dist/components/callout.js +68 -31
- package/dist/components/card.d.ts +34 -2
- package/dist/components/card.js +55 -18
- package/dist/components/cell.d.ts +5 -11
- package/dist/components/cell.js +11 -15
- package/dist/components/chart.d.ts +4 -10
- package/dist/components/chart.js +16 -24
- package/dist/components/details-string.d.ts +26 -0
- package/dist/components/details-string.js +39 -0
- package/dist/components/details.d.ts +32 -10
- package/dist/components/details.js +28 -25
- package/dist/components/divider.js +12 -16
- package/dist/components/figure.d.ts +5 -16
- package/dist/components/figure.js +9 -19
- package/dist/components/index.d.ts +14 -10
- package/dist/components/index.js +19 -16
- package/dist/components/kbd.d.ts +1 -1
- package/dist/components/kbd.js +3 -2
- package/dist/components/layout-wrapper.d.ts +7 -22
- package/dist/components/layout-wrapper.js +9 -23
- package/dist/components/progress.d.ts +3 -11
- package/dist/components/progress.js +8 -17
- package/dist/components/rating.d.ts +1 -2
- package/dist/components/rating.js +3 -4
- package/dist/components/row.d.ts +35 -20
- package/dist/components/row.js +35 -51
- package/dist/components/stat.d.ts +3 -8
- package/dist/components/stat.js +5 -11
- package/dist/components/tab.d.ts +9 -8
- package/dist/components/tab.js +12 -10
- package/dist/components/table-grid.d.ts +2 -1
- package/dist/components/table-grid.js +14 -12
- package/dist/components/table.d.ts +5 -10
- package/dist/components/table.js +8 -13
- package/dist/components/tabs-string.d.ts +17 -0
- package/dist/components/tabs-string.js +30 -0
- package/dist/components/tabs.d.ts +31 -14
- package/dist/components/tabs.js +55 -30
- package/dist/failure-presentation.d.ts +15 -0
- package/dist/failure-presentation.js +22 -0
- package/dist/glyphs.d.ts +73 -0
- package/dist/glyphs.js +88 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +10 -7
- package/dist/ink-string.d.ts +23 -0
- package/dist/ink-string.js +65 -0
- package/dist/interactive.d.ts +66 -0
- package/dist/interactive.js +207 -0
- package/dist/layout.js +1 -2
- package/dist/pack-loader.d.ts +64 -0
- package/dist/pack-loader.js +172 -0
- package/dist/pack-script.d.ts +44 -0
- package/dist/pack-script.js +135 -0
- package/dist/registry.d.ts +106 -100
- package/dist/registry.js +17 -9
- package/dist/render.d.ts +63 -25
- package/dist/render.js +1001 -380
- package/dist/text-grid.d.ts +90 -0
- package/dist/text-grid.js +300 -0
- package/package.json +9 -5
- package/dist/box.d.ts +0 -47
- package/dist/box.js +0 -209
- package/dist/measure.d.ts +0 -25
- package/dist/measure.js +0 -108
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sadigaxund
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/ansi.d.ts
CHANGED
|
@@ -39,6 +39,32 @@ export declare const italic: (text: string, level: ColorLevel) => string;
|
|
|
39
39
|
export declare const underline: (text: string, level: ColorLevel) => string;
|
|
40
40
|
/** SGR inverse/reverse video (7). Returns `text` unchanged at `'none'`. */
|
|
41
41
|
export declare const inverse: (text: string, level: ColorLevel) => string;
|
|
42
|
+
/**
|
|
43
|
+
* Repairs an SGR escape span that would otherwise cross an embedded `\n`
|
|
44
|
+
* inside `text`, so every physical LINE is independently self-contained:
|
|
45
|
+
* whatever SGR codes are still "open" (emitted since the last full reset)
|
|
46
|
+
* at a line boundary are closed with a reset before the `\n` and reopened
|
|
47
|
+
* at the start of the next line.
|
|
48
|
+
*
|
|
49
|
+
* This is the SYSTEMATIC fix for a real Ink behavior discovered during
|
|
50
|
+
* batch 10's post-phase-2 review: Ink's `<Text>` does not treat an embedded
|
|
51
|
+
* raw ANSI escape as transparent across an embedded newline the way a
|
|
52
|
+
* terminal receiving the same bytes directly would — it resets SGR state
|
|
53
|
+
* at every line boundary within one `<Text>` value and does not reopen it.
|
|
54
|
+
* Every multi-line string this engine hands to a single `<Text>` node
|
|
55
|
+
* (`render.tsx`'s `styledText` helper) MUST pass through this function
|
|
56
|
+
* first: a dim-wrapped multi-line frame, a bold-and-underlined heading that
|
|
57
|
+
* wrapped onto more than one line, a paragraph whose bold span happened to
|
|
58
|
+
* straddle a wrap point — anything this engine itself line-wrapped (with
|
|
59
|
+
* `text-grid.ts`'s `wrapText`) while carrying an open style. It is a no-op,
|
|
60
|
+
* fast-pathed, for a string with no `\n` at all.
|
|
61
|
+
*
|
|
62
|
+
* Every SGR code this engine's own helpers ever emit closes with a FULL
|
|
63
|
+
* reset (`\x1b[0m`), never a narrow one (`ansi.ts`'s `wrapSgr`), so
|
|
64
|
+
* tracking "what is currently open" only ever needs a flat list cleared by
|
|
65
|
+
* the next `\x1b[0m` (or a bare `\x1b[m`) — never per-attribute bookkeeping.
|
|
66
|
+
*/
|
|
67
|
+
export declare function carrySgrAcrossLines(text: string): string;
|
|
42
68
|
/**
|
|
43
69
|
* OSC 8: `ESC ] 8 ; ; url BEL text ESC ] 8 ; ; BEL`. Emitted only when
|
|
44
70
|
* `level` is not `'none'`; at `'none'` this returns `text` UNCHANGED, and
|
package/dist/ansi.js
CHANGED
|
@@ -63,6 +63,57 @@ export const italic = sgrWrap(3);
|
|
|
63
63
|
export const underline = sgrWrap(4);
|
|
64
64
|
/** SGR inverse/reverse video (7). Returns `text` unchanged at `'none'`. */
|
|
65
65
|
export const inverse = sgrWrap(7);
|
|
66
|
+
/** Matches one SGR sequence, capturing its raw parameter string (empty for a bare `ESC [ m`). */
|
|
67
|
+
const SGR_TOKEN = /\x1b\[([0-9;]*)m/g;
|
|
68
|
+
/**
|
|
69
|
+
* Repairs an SGR escape span that would otherwise cross an embedded `\n`
|
|
70
|
+
* inside `text`, so every physical LINE is independently self-contained:
|
|
71
|
+
* whatever SGR codes are still "open" (emitted since the last full reset)
|
|
72
|
+
* at a line boundary are closed with a reset before the `\n` and reopened
|
|
73
|
+
* at the start of the next line.
|
|
74
|
+
*
|
|
75
|
+
* This is the SYSTEMATIC fix for a real Ink behavior discovered during
|
|
76
|
+
* batch 10's post-phase-2 review: Ink's `<Text>` does not treat an embedded
|
|
77
|
+
* raw ANSI escape as transparent across an embedded newline the way a
|
|
78
|
+
* terminal receiving the same bytes directly would — it resets SGR state
|
|
79
|
+
* at every line boundary within one `<Text>` value and does not reopen it.
|
|
80
|
+
* Every multi-line string this engine hands to a single `<Text>` node
|
|
81
|
+
* (`render.tsx`'s `styledText` helper) MUST pass through this function
|
|
82
|
+
* first: a dim-wrapped multi-line frame, a bold-and-underlined heading that
|
|
83
|
+
* wrapped onto more than one line, a paragraph whose bold span happened to
|
|
84
|
+
* straddle a wrap point — anything this engine itself line-wrapped (with
|
|
85
|
+
* `text-grid.ts`'s `wrapText`) while carrying an open style. It is a no-op,
|
|
86
|
+
* fast-pathed, for a string with no `\n` at all.
|
|
87
|
+
*
|
|
88
|
+
* Every SGR code this engine's own helpers ever emit closes with a FULL
|
|
89
|
+
* reset (`\x1b[0m`), never a narrow one (`ansi.ts`'s `wrapSgr`), so
|
|
90
|
+
* tracking "what is currently open" only ever needs a flat list cleared by
|
|
91
|
+
* the next `\x1b[0m` (or a bare `\x1b[m`) — never per-attribute bookkeeping.
|
|
92
|
+
*/
|
|
93
|
+
export function carrySgrAcrossLines(text) {
|
|
94
|
+
if (!text.includes('\n'))
|
|
95
|
+
return text;
|
|
96
|
+
const lines = text.split('\n');
|
|
97
|
+
let openCodes = [];
|
|
98
|
+
const result = [];
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
const prefix = openCodes.map((params) => `${ESC}[${params}m`).join('');
|
|
101
|
+
SGR_TOKEN.lastIndex = 0;
|
|
102
|
+
let match;
|
|
103
|
+
while ((match = SGR_TOKEN.exec(line)) !== null) {
|
|
104
|
+
const params = match[1] ?? '';
|
|
105
|
+
if (params === '' || params === '0') {
|
|
106
|
+
openCodes = [];
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
openCodes = [...openCodes, params];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const suffix = openCodes.length > 0 ? RESET : '';
|
|
113
|
+
result.push(`${prefix}${line}${suffix}`);
|
|
114
|
+
}
|
|
115
|
+
return result.join('\n');
|
|
116
|
+
}
|
|
66
117
|
/**
|
|
67
118
|
* OSC 8: `ESC ] 8 ; ; url BEL text ESC ] 8 ; ; BEL`. Emitted only when
|
|
68
119
|
* `level` is not `'none'`; at `'none'` this returns `text` UNCHANGED, and
|
package/dist/components/badge.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { childrenText } from '../registry.js';
|
|
1
2
|
const BADGE_VARIANTS = [
|
|
2
3
|
'neutral',
|
|
3
4
|
'info',
|
|
@@ -31,14 +32,14 @@ const TOKEN_NAME = {
|
|
|
31
32
|
* variant. At color level `'none'` there is no inverse video to distinguish
|
|
32
33
|
* a badge from surrounding text, so it degrades to `[label]` instead.
|
|
33
34
|
*/
|
|
34
|
-
export const Badge = (attributes, children, ctx) => {
|
|
35
|
+
export const Badge = ({ attributes, children, ctx }) => {
|
|
35
36
|
const rawVariant = attributes.variant ?? DEFAULT_VARIANT;
|
|
36
37
|
const variant = isBadgeVariant(rawVariant)
|
|
37
38
|
? rawVariant
|
|
38
39
|
: DEFAULT_VARIANT;
|
|
39
|
-
const
|
|
40
|
+
const text = childrenText(children);
|
|
40
41
|
if (ctx.color === 'none')
|
|
41
|
-
return `[${
|
|
42
|
+
return `[${text}]`;
|
|
42
43
|
const token = TOKEN_NAME[VARIANT_TOKEN[variant]];
|
|
43
|
-
return ctx.inverse(ctx.style(` ${
|
|
44
|
+
return ctx.inverse(ctx.style(` ${text} `, token));
|
|
44
45
|
};
|
|
@@ -1,20 +1,45 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GlyphSet } from '../glyphs.js';
|
|
2
|
+
import type { ResolvedLayoutPresets } from '../layout.js';
|
|
3
|
+
import { type AnsiComponent, type DirectiveAttributes } from '../registry.js';
|
|
4
|
+
import type { Tier1Token } from '../theme.js';
|
|
2
5
|
export type CalloutType = 'info' | 'warning' | 'danger';
|
|
6
|
+
export declare const CALLOUT_TOKENS: Record<CalloutType, Tier1Token>;
|
|
7
|
+
export declare function isCalloutType(value: string): value is CalloutType;
|
|
8
|
+
/** `headerText` for `type`, e.g. `ℹ Info` — needed by both `resolveCalloutInnerWidth` (before there is a component instance) and `Callout` itself. */
|
|
9
|
+
export declare function headerTextFor(type: CalloutType, glyphs: GlyphSet): string;
|
|
10
|
+
/** The resolved `type` of a callout: an unknown or missing value is `info`. */
|
|
11
|
+
export declare function resolveCalloutType(attributes: DirectiveAttributes): CalloutType;
|
|
12
|
+
/**
|
|
13
|
+
* The callout's own OUTER (bar-including) width: what `Callout` draws at
|
|
14
|
+
* and what the Ink element form in `render.tsx` sizes its `Box` to.
|
|
15
|
+
* `title` is expected already sanitized, as `Callout` passes it.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveCalloutBoxWidth(type: CalloutType, title: string | null, layout: ResolvedLayoutPresets | undefined, outerWidth: number, glyphs: GlyphSet): number;
|
|
18
|
+
/**
|
|
19
|
+
* The inner budget `callout`'s body renders at, given ONLY its own
|
|
20
|
+
* attributes and the outer width — never the body itself. Mirrors
|
|
21
|
+
* `card.ts`'s `resolveCardInnerWidth`: `render.tsx`'s walk calls this
|
|
22
|
+
* BEFORE building children, so a callout's body (and any self-drawing
|
|
23
|
+
* component nested inside it, like a `card`) is built exactly once, at the
|
|
24
|
+
* correct width, never built wide and re-wrapped narrower afterward — see
|
|
25
|
+
* `resolveCardInnerWidth`'s doc comment for why that re-wrap-afterward
|
|
26
|
+
* shape was a real, shipped regression.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveCalloutInnerWidth(attributes: DirectiveAttributes, layout: ResolvedLayoutPresets | undefined, outerWidth: number, glyphs: GlyphSet): number;
|
|
3
29
|
/**
|
|
4
30
|
* `:::callout{type=info|warning|danger title="..." text=left|center|right}` —
|
|
5
31
|
* a colored aside/warning/danger box. Unknown/missing `type` falls back to
|
|
6
|
-
* `info
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* width. `text` aligns the icon/title/body lines within that remaining
|
|
11
|
-
* width; absent/invalid `text` behaves as `left` (no padding needed).
|
|
32
|
+
* `info`. Terminal form: every line carries a colored left bar; the first
|
|
33
|
+
* line is the icon plus the type label, an optional bold title line
|
|
34
|
+
* follows, then the body. `text` aligns the icon/title/body lines within
|
|
35
|
+
* the remaining width.
|
|
12
36
|
*
|
|
13
|
-
* Registered `selfLayout
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* `
|
|
18
|
-
*
|
|
37
|
+
* Registered `selfLayout`: every line carries the colored bar prefix, which
|
|
38
|
+
* a generic post-render `applyLayout` narrowing would re-wrap right through,
|
|
39
|
+
* losing the bar on any continuation line. This component reads the
|
|
40
|
+
* resolved `width`/`align` off `ctx.layout` itself and sizes/places its own
|
|
41
|
+
* bar block, exactly like `card`/`table`/`divider`. `children` arrives
|
|
42
|
+
* ALREADY built at exactly this component's own inner width
|
|
43
|
+
* (`resolveCalloutInnerWidth` above) — this component never re-wraps.
|
|
19
44
|
*/
|
|
20
45
|
export declare const Callout: AnsiComponent;
|
|
@@ -1,69 +1,106 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { measureWidth, padText } from '../text-grid.js';
|
|
2
2
|
import { selfLayoutAlign, selfLayoutWidth } from '../layout.js';
|
|
3
|
+
import { childrenText, } from '../registry.js';
|
|
3
4
|
const CALLOUT_TYPES = ['info', 'warning', 'danger'];
|
|
4
|
-
const CALLOUT_ICONS = {
|
|
5
|
-
info: 'ℹ',
|
|
6
|
-
warning: '▲',
|
|
7
|
-
danger: '✕',
|
|
8
|
-
};
|
|
9
5
|
const CALLOUT_LABELS = {
|
|
10
6
|
info: 'Info',
|
|
11
7
|
warning: 'Warning',
|
|
12
8
|
danger: 'Danger',
|
|
13
9
|
};
|
|
14
|
-
const CALLOUT_TOKENS = {
|
|
10
|
+
export const CALLOUT_TOKENS = {
|
|
15
11
|
info: '--mk-info',
|
|
16
12
|
warning: '--mk-warning',
|
|
17
13
|
danger: '--mk-danger',
|
|
18
14
|
};
|
|
19
|
-
function isCalloutType(value) {
|
|
15
|
+
export function isCalloutType(value) {
|
|
20
16
|
return CALLOUT_TYPES.includes(value);
|
|
21
17
|
}
|
|
22
18
|
const TEXT_ALIGNS = ['left', 'center', 'right'];
|
|
23
19
|
function isTextAlign(value) {
|
|
24
20
|
return TEXT_ALIGNS.includes(value);
|
|
25
21
|
}
|
|
26
|
-
/**
|
|
27
|
-
|
|
22
|
+
/** `headerText` for `type`, e.g. `ℹ Info` — needed by both `resolveCalloutInnerWidth` (before there is a component instance) and `Callout` itself. */
|
|
23
|
+
export function headerTextFor(type, glyphs) {
|
|
24
|
+
return `${glyphs.calloutIcons[type]} ${CALLOUT_LABELS[type]}`;
|
|
25
|
+
}
|
|
26
|
+
/** The resolved `type` of a callout: an unknown or missing value is `info`. */
|
|
27
|
+
export function resolveCalloutType(attributes) {
|
|
28
|
+
const raw = attributes.type ?? 'info';
|
|
29
|
+
return isCalloutType(raw) ? raw : 'info';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The callout's own OUTER (bar-including) width: what `Callout` draws at
|
|
33
|
+
* and what the Ink element form in `render.tsx` sizes its `Box` to.
|
|
34
|
+
* `title` is expected already sanitized, as `Callout` passes it.
|
|
35
|
+
*/
|
|
36
|
+
export function resolveCalloutBoxWidth(type, title, layout, outerWidth, glyphs) {
|
|
37
|
+
const barWidth = measureWidth(glyphs.calloutBar);
|
|
38
|
+
const naturalWidth = barWidth +
|
|
39
|
+
Math.max(measureWidth(headerTextFor(type, glyphs)), measureWidth(title ?? ''));
|
|
40
|
+
return selfLayoutWidth(layout, outerWidth, naturalWidth);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The inner budget `callout`'s body renders at, given ONLY its own
|
|
44
|
+
* attributes and the outer width — never the body itself. Mirrors
|
|
45
|
+
* `card.ts`'s `resolveCardInnerWidth`: `render.tsx`'s walk calls this
|
|
46
|
+
* BEFORE building children, so a callout's body (and any self-drawing
|
|
47
|
+
* component nested inside it, like a `card`) is built exactly once, at the
|
|
48
|
+
* correct width, never built wide and re-wrapped narrower afterward — see
|
|
49
|
+
* `resolveCardInnerWidth`'s doc comment for why that re-wrap-afterward
|
|
50
|
+
* shape was a real, shipped regression.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveCalloutInnerWidth(attributes, layout, outerWidth, glyphs) {
|
|
53
|
+
// The left bar every line of a callout carries, marking it as one
|
|
54
|
+
// colored block at a glance.
|
|
55
|
+
const barWidth = measureWidth(glyphs.calloutBar);
|
|
56
|
+
const rawType = attributes.type ?? 'info';
|
|
57
|
+
const type = isCalloutType(rawType) ? rawType : 'info';
|
|
58
|
+
const title = attributes.title ?? null;
|
|
59
|
+
const naturalWidth = barWidth +
|
|
60
|
+
Math.max(measureWidth(headerTextFor(type, glyphs)), measureWidth(title ?? ''));
|
|
61
|
+
const boxWidth = selfLayoutWidth(layout, outerWidth, naturalWidth);
|
|
62
|
+
return Math.max(1, boxWidth - barWidth);
|
|
63
|
+
}
|
|
28
64
|
/**
|
|
29
65
|
* `:::callout{type=info|warning|danger title="..." text=left|center|right}` —
|
|
30
66
|
* a colored aside/warning/danger box. Unknown/missing `type` falls back to
|
|
31
|
-
* `info
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* width. `text` aligns the icon/title/body lines within that remaining
|
|
36
|
-
* width; absent/invalid `text` behaves as `left` (no padding needed).
|
|
67
|
+
* `info`. Terminal form: every line carries a colored left bar; the first
|
|
68
|
+
* line is the icon plus the type label, an optional bold title line
|
|
69
|
+
* follows, then the body. `text` aligns the icon/title/body lines within
|
|
70
|
+
* the remaining width.
|
|
37
71
|
*
|
|
38
|
-
* Registered `selfLayout
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* `
|
|
43
|
-
*
|
|
72
|
+
* Registered `selfLayout`: every line carries the colored bar prefix, which
|
|
73
|
+
* a generic post-render `applyLayout` narrowing would re-wrap right through,
|
|
74
|
+
* losing the bar on any continuation line. This component reads the
|
|
75
|
+
* resolved `width`/`align` off `ctx.layout` itself and sizes/places its own
|
|
76
|
+
* bar block, exactly like `card`/`table`/`divider`. `children` arrives
|
|
77
|
+
* ALREADY built at exactly this component's own inner width
|
|
78
|
+
* (`resolveCalloutInnerWidth` above) — this component never re-wraps.
|
|
44
79
|
*/
|
|
45
|
-
export const Callout = (attributes, children, ctx) => {
|
|
80
|
+
export const Callout = ({ attributes, children, ctx }) => {
|
|
46
81
|
const rawType = attributes.type ?? 'info';
|
|
47
82
|
const type = isCalloutType(rawType) ? rawType : 'info';
|
|
48
83
|
const token = CALLOUT_TOKENS[type];
|
|
49
84
|
const title = attributes.title ?? null;
|
|
50
85
|
const rawTextAlign = attributes.text;
|
|
51
86
|
const align = rawTextAlign && isTextAlign(rawTextAlign) ? rawTextAlign : 'left';
|
|
52
|
-
const headerText =
|
|
87
|
+
const headerText = headerTextFor(type, ctx.glyphs);
|
|
53
88
|
const titleText = title ? ctx.text(title) : undefined;
|
|
54
|
-
const
|
|
89
|
+
const barWidth = measureWidth(ctx.glyphs.calloutBar);
|
|
90
|
+
const naturalWidth = barWidth +
|
|
91
|
+
Math.max(measureWidth(headerText), measureWidth(titleText ?? ''));
|
|
55
92
|
const boxWidth = selfLayoutWidth(ctx.layout, ctx.width, naturalWidth);
|
|
56
|
-
const bar = ctx.style(
|
|
57
|
-
const innerWidth = Math.max(1, boxWidth -
|
|
58
|
-
const placeLine = (line) => align === 'left' ? line :
|
|
93
|
+
const bar = ctx.style(ctx.glyphs.calloutBar, token);
|
|
94
|
+
const innerWidth = Math.max(1, boxWidth - barWidth);
|
|
95
|
+
const placeLine = (line) => align === 'left' ? line : padText(line, innerWidth, align);
|
|
59
96
|
const headerLine = `${bar}${placeLine(ctx.style(headerText, token))}`;
|
|
60
97
|
const lines = [headerLine];
|
|
61
98
|
if (titleText) {
|
|
62
99
|
lines.push(`${bar}${placeLine(ctx.bold(titleText))}`);
|
|
63
100
|
}
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
66
|
-
for (const line of
|
|
101
|
+
const body = childrenText(children);
|
|
102
|
+
if (body) {
|
|
103
|
+
for (const line of body.split('\n')) {
|
|
67
104
|
lines.push(`${bar}${placeLine(line)}`);
|
|
68
105
|
}
|
|
69
106
|
}
|
|
@@ -1,4 +1,33 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ResolvedLayoutPresets } from '../layout.js';
|
|
2
|
+
import { type AnsiComponent, type DirectiveAttributes } from '../registry.js';
|
|
3
|
+
/** Frame border + one space of interior padding on each side. Exported: `render.tsx`'s Ink-`Box`-bordered ELEMENT builder needs the same overhead to go from a box width to an inner width. */
|
|
4
|
+
export declare const CARD_FRAME_OVERHEAD = 2;
|
|
5
|
+
/**
|
|
6
|
+
* `card`'s own natural/self-drawn OUTER (frame-including) width, given ONLY
|
|
7
|
+
* its own attributes, the resolved `width=`/`align=` layout preset, and the
|
|
8
|
+
* outer budget — never the body. Exported for `render.tsx`'s ELEMENT-mode
|
|
9
|
+
* builder, which needs the outer box width itself (to size its Ink `Box`)
|
|
10
|
+
* rather than only the inner budget `resolveCardInnerWidth` below returns.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveCardBoxWidth(attributes: DirectiveAttributes, layout: ResolvedLayoutPresets | undefined, outerWidth: number): number;
|
|
13
|
+
/**
|
|
14
|
+
* The inner budget `card`'s body renders at, given ONLY its own attributes
|
|
15
|
+
* and the outer width — never the body itself. This is deliberately a pure
|
|
16
|
+
* function callable BEFORE any children exist: `render.tsx`'s walk calls it
|
|
17
|
+
* to decide the width its OWN recursive children-builder uses, so `card`'s
|
|
18
|
+
* children are built ONCE, directly at the correct inner width, and never
|
|
19
|
+
* built wide and re-wrapped narrower afterward.
|
|
20
|
+
*
|
|
21
|
+
* That re-wrap-afterward shape was a real, shipped regression (caught
|
|
22
|
+
* post-phase-2 review): re-wrapping an ALREADY-RENDERED child block with a
|
|
23
|
+
* word-based wrapper corrupts anything in it that draws its own fixed-width
|
|
24
|
+
* glyphs (a nested `card`/`callout`'s frame, a nested `row`'s columns) — the
|
|
25
|
+
* word-wrapper has no notion that a run of box-drawing characters is not
|
|
26
|
+
* prose. Computing the inner width eagerly and building children exactly
|
|
27
|
+
* once at it is the fix, and it is what makes the deleted lazy-children API
|
|
28
|
+
* safe to have removed: this is its eager equivalent.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveCardInnerWidth(attributes: DirectiveAttributes, layout: ResolvedLayoutPresets | undefined, outerWidth: number): number;
|
|
2
31
|
/**
|
|
3
32
|
* `:::card{title="..." text=left|center|right} ... :::` — a titled panel.
|
|
4
33
|
* `title` is optional; the title is woven into the frame's top edge, not a
|
|
@@ -7,6 +36,9 @@ import type { AnsiComponent } from '../registry.js';
|
|
|
7
36
|
*
|
|
8
37
|
* Registered `selfLayout` (see `callout.ts`'s doc comment for why): this
|
|
9
38
|
* component draws a real box-drawn frame, which a generic post-render
|
|
10
|
-
* narrow/pad would corrupt, so it reads `ctx.layout` and sizes its own
|
|
39
|
+
* narrow/pad would corrupt, so it reads `ctx.layout` and sizes its own
|
|
40
|
+
* frame. `children` arrives ALREADY built at exactly this component's own
|
|
41
|
+
* inner width (`render.tsx`'s walk calls `resolveCardInnerWidth` above
|
|
42
|
+
* before building them) — this component never re-wraps.
|
|
11
43
|
*/
|
|
12
44
|
export declare const Card: AnsiComponent;
|
package/dist/components/card.js
CHANGED
|
@@ -1,13 +1,49 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { measureWidth, frameBlock, padText } from '../text-grid.js';
|
|
2
2
|
import { selfLayoutAlign, selfLayoutWidth } from '../layout.js';
|
|
3
|
+
import { childrenText, } from '../registry.js';
|
|
3
4
|
const TEXT_ALIGNS = ['left', 'center', 'right'];
|
|
4
5
|
function isTextAlign(value) {
|
|
5
6
|
return TEXT_ALIGNS.includes(value);
|
|
6
7
|
}
|
|
7
|
-
/** `width=fit` with no `title` and an unhelpfully-shaped body
|
|
8
|
+
/** `width=fit` with no `title` and an unhelpfully-shaped body falls back to this column count rather than the full available width. */
|
|
8
9
|
const FIT_DEFAULT_WIDTH = 40;
|
|
9
|
-
/** Frame border + one space of interior padding on each side. */
|
|
10
|
-
const
|
|
10
|
+
/** Frame border + one space of interior padding on each side. Exported: `render.tsx`'s Ink-`Box`-bordered ELEMENT builder needs the same overhead to go from a box width to an inner width. */
|
|
11
|
+
export const CARD_FRAME_OVERHEAD = 2;
|
|
12
|
+
/**
|
|
13
|
+
* `card`'s own natural/self-drawn OUTER (frame-including) width, given ONLY
|
|
14
|
+
* its own attributes, the resolved `width=`/`align=` layout preset, and the
|
|
15
|
+
* outer budget — never the body. Exported for `render.tsx`'s ELEMENT-mode
|
|
16
|
+
* builder, which needs the outer box width itself (to size its Ink `Box`)
|
|
17
|
+
* rather than only the inner budget `resolveCardInnerWidth` below returns.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveCardBoxWidth(attributes, layout, outerWidth) {
|
|
20
|
+
const title = attributes.title ?? null;
|
|
21
|
+
const naturalWidth = title
|
|
22
|
+
? measureWidth(title) + CARD_FRAME_OVERHEAD * 2
|
|
23
|
+
: FIT_DEFAULT_WIDTH;
|
|
24
|
+
return selfLayoutWidth(layout, outerWidth, naturalWidth);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The inner budget `card`'s body renders at, given ONLY its own attributes
|
|
28
|
+
* and the outer width — never the body itself. This is deliberately a pure
|
|
29
|
+
* function callable BEFORE any children exist: `render.tsx`'s walk calls it
|
|
30
|
+
* to decide the width its OWN recursive children-builder uses, so `card`'s
|
|
31
|
+
* children are built ONCE, directly at the correct inner width, and never
|
|
32
|
+
* built wide and re-wrapped narrower afterward.
|
|
33
|
+
*
|
|
34
|
+
* That re-wrap-afterward shape was a real, shipped regression (caught
|
|
35
|
+
* post-phase-2 review): re-wrapping an ALREADY-RENDERED child block with a
|
|
36
|
+
* word-based wrapper corrupts anything in it that draws its own fixed-width
|
|
37
|
+
* glyphs (a nested `card`/`callout`'s frame, a nested `row`'s columns) — the
|
|
38
|
+
* word-wrapper has no notion that a run of box-drawing characters is not
|
|
39
|
+
* prose. Computing the inner width eagerly and building children exactly
|
|
40
|
+
* once at it is the fix, and it is what makes the deleted lazy-children API
|
|
41
|
+
* safe to have removed: this is its eager equivalent.
|
|
42
|
+
*/
|
|
43
|
+
export function resolveCardInnerWidth(attributes, layout, outerWidth) {
|
|
44
|
+
const boxWidth = resolveCardBoxWidth(attributes, layout, outerWidth);
|
|
45
|
+
return Math.max(1, boxWidth - CARD_FRAME_OVERHEAD);
|
|
46
|
+
}
|
|
11
47
|
/**
|
|
12
48
|
* `:::card{title="..." text=left|center|right} ... :::` — a titled panel.
|
|
13
49
|
* `title` is optional; the title is woven into the frame's top edge, not a
|
|
@@ -16,29 +52,30 @@ const FRAME_OVERHEAD = 2;
|
|
|
16
52
|
*
|
|
17
53
|
* Registered `selfLayout` (see `callout.ts`'s doc comment for why): this
|
|
18
54
|
* component draws a real box-drawn frame, which a generic post-render
|
|
19
|
-
* narrow/pad would corrupt, so it reads `ctx.layout` and sizes its own
|
|
55
|
+
* narrow/pad would corrupt, so it reads `ctx.layout` and sizes its own
|
|
56
|
+
* frame. `children` arrives ALREADY built at exactly this component's own
|
|
57
|
+
* inner width (`render.tsx`'s walk calls `resolveCardInnerWidth` above
|
|
58
|
+
* before building them) — this component never re-wraps.
|
|
20
59
|
*/
|
|
21
|
-
export const Card = (attributes, children, ctx) => {
|
|
60
|
+
export const Card = ({ attributes, children, ctx }) => {
|
|
22
61
|
const title = attributes.title ?? null;
|
|
23
62
|
const titleText = title ? ctx.text(title) : undefined;
|
|
24
63
|
const rawTextAlign = attributes.text;
|
|
25
64
|
const align = rawTextAlign && isTextAlign(rawTextAlign) ? rawTextAlign : 'left';
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const body = childrenText
|
|
33
|
-
? childrenText
|
|
65
|
+
const boxWidth = resolveCardBoxWidth({ ...attributes, title: titleText ?? null }, ctx.layout, ctx.width);
|
|
66
|
+
const innerWidth = Math.max(1, boxWidth - CARD_FRAME_OVERHEAD);
|
|
67
|
+
const bodyText = childrenText(children);
|
|
68
|
+
const body = align === 'left'
|
|
69
|
+
? bodyText
|
|
70
|
+
: bodyText
|
|
34
71
|
.split('\n')
|
|
35
|
-
.map((line) =>
|
|
36
|
-
.join('\n')
|
|
37
|
-
|
|
38
|
-
const framed = ctx.frame(body, {
|
|
72
|
+
.map((line) => padText(line, innerWidth, align))
|
|
73
|
+
.join('\n');
|
|
74
|
+
const framed = frameBlock(body, {
|
|
39
75
|
style: 'solid',
|
|
40
76
|
title: titleText,
|
|
41
77
|
width: boxWidth,
|
|
78
|
+
glyphs: ctx.glyphs,
|
|
42
79
|
});
|
|
43
80
|
return selfLayoutAlign(framed, ctx.layout, ctx.width);
|
|
44
81
|
};
|
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AnsiComponent } from '../registry.js';
|
|
2
2
|
/**
|
|
3
3
|
* `:::cell ... :::` — a transparent grouping container whose only job is
|
|
4
4
|
* letting several blocks count as ONE cell of `:::row`.
|
|
5
5
|
*
|
|
6
|
-
* `row.ts` no longer discovers its cells by splitting a flattened string on
|
|
7
|
-
* a blank-line heuristic: it walks its own directive's top-level children
|
|
8
|
-
* directly (`registry.ts`'s `AnsiChildren.parts`), so a `cell` grouping more
|
|
9
|
-
* than one block renders those blocks exactly as it would standalone,
|
|
10
|
-
* blank line and all, with no collapsing needed to keep a cell boundary
|
|
11
|
-
* unambiguous for `row.ts`.
|
|
12
|
-
*
|
|
13
6
|
* `text` aligns this cell's own content when rendered STANDALONE (outside a
|
|
14
|
-
* `row`)
|
|
15
|
-
*
|
|
16
|
-
*
|
|
7
|
+
* `row`) or nested inside one: `render.tsx`'s `row` handling still runs
|
|
8
|
+
* each cell through the ordinary directive pipeline (so this component's
|
|
9
|
+
* own alignment applies first), then applies `row`'s OWN `text` uniformly
|
|
10
|
+
* on top — see `components/row.ts`'s doc comment.
|
|
17
11
|
*/
|
|
18
12
|
export declare const Cell: AnsiComponent;
|
package/dist/components/cell.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { padText } from '../text-grid.js';
|
|
2
|
+
import { childrenText } from '../registry.js';
|
|
1
3
|
const TEXT_ALIGNS = ['left', 'center', 'right'];
|
|
2
4
|
function isTextAlign(value) {
|
|
3
5
|
return TEXT_ALIGNS.includes(value);
|
|
@@ -6,26 +8,20 @@ function isTextAlign(value) {
|
|
|
6
8
|
* `:::cell ... :::` — a transparent grouping container whose only job is
|
|
7
9
|
* letting several blocks count as ONE cell of `:::row`.
|
|
8
10
|
*
|
|
9
|
-
* `row.ts` no longer discovers its cells by splitting a flattened string on
|
|
10
|
-
* a blank-line heuristic: it walks its own directive's top-level children
|
|
11
|
-
* directly (`registry.ts`'s `AnsiChildren.parts`), so a `cell` grouping more
|
|
12
|
-
* than one block renders those blocks exactly as it would standalone,
|
|
13
|
-
* blank line and all, with no collapsing needed to keep a cell boundary
|
|
14
|
-
* unambiguous for `row.ts`.
|
|
15
|
-
*
|
|
16
11
|
* `text` aligns this cell's own content when rendered STANDALONE (outside a
|
|
17
|
-
* `row`)
|
|
18
|
-
*
|
|
19
|
-
*
|
|
12
|
+
* `row`) or nested inside one: `render.tsx`'s `row` handling still runs
|
|
13
|
+
* each cell through the ordinary directive pipeline (so this component's
|
|
14
|
+
* own alignment applies first), then applies `row`'s OWN `text` uniformly
|
|
15
|
+
* on top — see `components/row.ts`'s doc comment.
|
|
20
16
|
*/
|
|
21
|
-
export const Cell = (attributes, children, ctx) => {
|
|
17
|
+
export const Cell = ({ attributes, children, ctx }) => {
|
|
22
18
|
const rawTextAlign = attributes.text;
|
|
23
19
|
const align = rawTextAlign && isTextAlign(rawTextAlign) ? rawTextAlign : undefined;
|
|
24
|
-
const
|
|
20
|
+
const text = childrenText(children);
|
|
25
21
|
if (!align || align === 'left')
|
|
26
|
-
return
|
|
27
|
-
return
|
|
22
|
+
return text;
|
|
23
|
+
return text
|
|
28
24
|
.split('\n')
|
|
29
|
-
.map((line) =>
|
|
25
|
+
.map((line) => padText(line, ctx.width, align))
|
|
30
26
|
.join('\n');
|
|
31
27
|
};
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import type { AnsiComponent } from '../registry.js';
|
|
2
2
|
/**
|
|
3
3
|
* `::chart{kind=line|bar values="1,3,2,5"}` — a dependency-free, hand-rolled
|
|
4
|
-
* chart. Data binding (§8)
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
* a small neutral "no data" line rather than a broken chart.
|
|
9
|
-
*
|
|
10
|
-
* Terminal form: a `line` chart is a single sparkline row (`▁▂▃▄▅▆▇█`),
|
|
11
|
-
* flanked by its minimum and maximum as plain numbers. A `bar` chart is one
|
|
12
|
-
* horizontal `█` bar per point, its own value as the row's label, labels
|
|
13
|
-
* right-aligned to the widest one so every bar starts at the same column.
|
|
4
|
+
* chart. Data binding (§8): a bound `data` array of numbers (or `{value}`
|
|
5
|
+
* objects) takes priority over the static `values=` attribute. Terminal
|
|
6
|
+
* form: a `line` chart is a single sparkline row, a `bar` chart is one
|
|
7
|
+
* horizontal bar per point.
|
|
14
8
|
*/
|
|
15
9
|
export declare const Chart: AnsiComponent;
|