@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
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { layoutWrapperAxis } from '@markii/stdlib';
|
|
2
2
|
import { applyLayout } from '../layout.js';
|
|
3
|
+
import { childrenText } from '../registry.js';
|
|
3
4
|
/**
|
|
4
5
|
* The closed set of layout-wrapper container names (docs/format.md):
|
|
5
|
-
* aliases of the one shared implementation below (`createLayoutWrapper`)
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `left`/`right` are align presets, `wide`/`narrow`/`full`/`fit` are width
|
|
10
|
-
* presets), which is what lets `createLayoutWrapper` use the preset name
|
|
11
|
-
* itself as that axis's value with no separate lookup table.
|
|
6
|
+
* aliases of the one shared implementation below (`createLayoutWrapper`).
|
|
7
|
+
* There is deliberately no `normal` alias: the default needs no wrapper at
|
|
8
|
+
* all. Every name here is ALSO one of `@markii/stdlib`'s own width/align
|
|
9
|
+
* preset values.
|
|
12
10
|
*/
|
|
13
11
|
export const LAYOUT_WRAPPER_PRESETS = [
|
|
14
12
|
'center',
|
|
@@ -22,20 +20,8 @@ export const LAYOUT_WRAPPER_PRESETS = [
|
|
|
22
20
|
/**
|
|
23
21
|
* Creates the registry component for one of docs/format.md's layout-wrapper
|
|
24
22
|
* container names. One shared implementation, bound to `preset` at
|
|
25
|
-
* registration time
|
|
26
|
-
*
|
|
27
|
-
* both reserved keys before this ever runs.
|
|
28
|
-
*
|
|
29
|
-
* A wrapper sets ONE axis by its own NAME (docs/spec.md §3): `preset` itself
|
|
30
|
-
* IS that axis's value (`center` sets `align: 'center'`; `narrow` sets
|
|
31
|
-
* `width: 'narrow'`), so it is applied unconditionally, whatever the author
|
|
32
|
-
* wrote for that axis's own reserved attribute (already discarded — the
|
|
33
|
-
* name always wins). The OTHER axis, when the author supplied it, arrives
|
|
34
|
-
* as `ctx.layout` (`render.ts` resolved it on this wrapper's behalf, since
|
|
35
|
-
* this wrapper is the directive's registered `layout` scope). Both are
|
|
36
|
-
* merged into one `ResolvedLayoutPresets` and applied together via
|
|
37
|
-
* `../layout.js`'s `applyLayout`, so `:::center{width=fit}` narrows AND
|
|
38
|
-
* centers in one pass.
|
|
23
|
+
* registration time: it never reads `attributes` at all — `render.tsx`
|
|
24
|
+
* already stripped both reserved keys before this ever runs.
|
|
39
25
|
*/
|
|
40
26
|
export function createLayoutWrapper(preset) {
|
|
41
27
|
const ownAxis = layoutWrapperAxis(preset);
|
|
@@ -46,9 +32,9 @@ export function createLayoutWrapper(preset) {
|
|
|
46
32
|
const own = ownAxis === 'align'
|
|
47
33
|
? { align: preset }
|
|
48
34
|
: { width: preset };
|
|
49
|
-
return (
|
|
35
|
+
return ({ children, ctx }) => {
|
|
50
36
|
const merged = { ...own, ...ctx.layout };
|
|
51
|
-
return applyLayout(children
|
|
37
|
+
return applyLayout(childrenText(children), merged, ctx.width);
|
|
52
38
|
};
|
|
53
39
|
}
|
|
54
40
|
/** The layout axis `preset` sets by its own name. Mirrors `@markii/html`'s `layoutWrapperPresetAxis`. */
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import type { AnsiComponent } from '../registry.js';
|
|
2
2
|
/**
|
|
3
|
-
* `::progress{value=3 max=5 label="tasks"}` — a meter bar. Data binding
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Non-numeric/`NaN`/`Infinity` input falls back to `0` (value) or the
|
|
7
|
-
* default `max` of `1`; the effective value is clamped to `[0, max]`.
|
|
8
|
-
* Terminal form: `label bar percent`, the bar drawn with `█` (filled) and
|
|
9
|
-
* `░` (empty) at up to `MAX_BAR_WIDTH` columns, narrower when the label and
|
|
10
|
-
* percent text leave less room. `format`/`decimals`, when given, format the
|
|
11
|
-
* `value/max` fraction for the percent readout in place of the default
|
|
12
|
-
* rounded integer percent, matching `@markii/html` exactly. A failed/stale
|
|
13
|
-
* binding appends the quiet failure suffix after the percent.
|
|
3
|
+
* `::progress{value=3 max=5 label="tasks"}` — a meter bar. Data binding
|
|
4
|
+
* (§8): a bound number supplies `value`; a bound object may supply
|
|
5
|
+
* `value`/`max` — explicit attributes always win.
|
|
14
6
|
*/
|
|
15
7
|
export declare const Progress: AnsiComponent;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { formatValue } from '@markii/stdlib';
|
|
2
|
-
import {
|
|
2
|
+
import { measureWidth } from '../text-grid.js';
|
|
3
3
|
import { safeRead } from '../resolve.js';
|
|
4
4
|
import { dataStateSuffix, failureToken } from '../failure-presentation.js';
|
|
5
5
|
const DEFAULT_MAX = 1;
|
|
@@ -33,24 +33,14 @@ function readProgressFields(data) {
|
|
|
33
33
|
return {};
|
|
34
34
|
}
|
|
35
35
|
/** Block characters the bar fills with, and its widest allowed footprint (columns), sized down further when the label/percent text leaves less room. */
|
|
36
|
-
const FILLED_CHAR = '█';
|
|
37
|
-
const EMPTY_CHAR = '░';
|
|
38
36
|
const MAX_BAR_WIDTH = 30;
|
|
39
37
|
const MIN_BAR_WIDTH = 3;
|
|
40
38
|
/**
|
|
41
|
-
* `::progress{value=3 max=5 label="tasks"}` — a meter bar. Data binding
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* Non-numeric/`NaN`/`Infinity` input falls back to `0` (value) or the
|
|
45
|
-
* default `max` of `1`; the effective value is clamped to `[0, max]`.
|
|
46
|
-
* Terminal form: `label bar percent`, the bar drawn with `█` (filled) and
|
|
47
|
-
* `░` (empty) at up to `MAX_BAR_WIDTH` columns, narrower when the label and
|
|
48
|
-
* percent text leave less room. `format`/`decimals`, when given, format the
|
|
49
|
-
* `value/max` fraction for the percent readout in place of the default
|
|
50
|
-
* rounded integer percent, matching `@markii/html` exactly. A failed/stale
|
|
51
|
-
* binding appends the quiet failure suffix after the percent.
|
|
39
|
+
* `::progress{value=3 max=5 label="tasks"}` — a meter bar. Data binding
|
|
40
|
+
* (§8): a bound number supplies `value`; a bound object may supply
|
|
41
|
+
* `value`/`max` — explicit attributes always win.
|
|
52
42
|
*/
|
|
53
|
-
export const Progress = (attributes,
|
|
43
|
+
export const Progress = ({ attributes, ctx }) => {
|
|
54
44
|
const { data, dataStatus, dataFailureKind } = ctx;
|
|
55
45
|
const bound = safeRead(() => dataStatus === 'missing' || dataStatus === 'error'
|
|
56
46
|
? {}
|
|
@@ -70,9 +60,10 @@ export const Progress = (attributes, _children, ctx) => {
|
|
|
70
60
|
const suffix = dataStateSuffix(dataStatus, dataFailureKind);
|
|
71
61
|
const token = failureToken(dataFailureKind);
|
|
72
62
|
const percentPart = ` ${ctx.text(percentText)}${suffix ? (token ? ctx.style(suffix, token) : ctx.dim(suffix)) : ''}`;
|
|
73
|
-
const overhead =
|
|
63
|
+
const overhead = measureWidth(labelPart) + measureWidth(percentText) + 1;
|
|
74
64
|
const barWidth = Math.max(MIN_BAR_WIDTH, Math.min(MAX_BAR_WIDTH, ctx.width - overhead));
|
|
75
65
|
const filledCount = Math.round((percent / 100) * barWidth);
|
|
76
|
-
const bar =
|
|
66
|
+
const bar = ctx.glyphs.barFilled.repeat(filledCount) +
|
|
67
|
+
ctx.glyphs.barEmpty.repeat(barWidth - filledCount);
|
|
77
68
|
return `${labelPart}${bar}${percentPart}`;
|
|
78
69
|
};
|
|
@@ -3,7 +3,6 @@ import type { AnsiComponent } from '../registry.js';
|
|
|
3
3
|
* `::rating{value=3 max=5}` — a leaf directive rendering a row of stars.
|
|
4
4
|
* Both attributes are optional and clamped to sane bounds; malformed input
|
|
5
5
|
* degrades gracefully instead of throwing. Terminal form: filled (`★`) and
|
|
6
|
-
* empty (`☆`) stars, one per position up to `max`.
|
|
7
|
-
* `@markii/html`'s `Rating` clamping rules exactly.
|
|
6
|
+
* empty (`☆`) stars, one per position up to `max`.
|
|
8
7
|
*/
|
|
9
8
|
export declare const Rating: AnsiComponent;
|
|
@@ -14,15 +14,14 @@ function clamp(value, min, max) {
|
|
|
14
14
|
* `::rating{value=3 max=5}` — a leaf directive rendering a row of stars.
|
|
15
15
|
* Both attributes are optional and clamped to sane bounds; malformed input
|
|
16
16
|
* degrades gracefully instead of throwing. Terminal form: filled (`★`) and
|
|
17
|
-
* empty (`☆`) stars, one per position up to `max`.
|
|
18
|
-
* `@markii/html`'s `Rating` clamping rules exactly.
|
|
17
|
+
* empty (`☆`) stars, one per position up to `max`.
|
|
19
18
|
*/
|
|
20
|
-
export const Rating = (attributes) => {
|
|
19
|
+
export const Rating = ({ attributes, ctx }) => {
|
|
21
20
|
const max = clamp(parseCount(attributes.max, DEFAULT_MAX), MIN_MAX, MAX_MAX);
|
|
22
21
|
const value = clamp(parseCount(attributes.value, 0), 0, max);
|
|
23
22
|
let stars = '';
|
|
24
23
|
for (let index = 0; index < max; index += 1) {
|
|
25
|
-
stars += index < value ?
|
|
24
|
+
stars += index < value ? ctx.glyphs.starFilled : ctx.glyphs.starEmpty;
|
|
26
25
|
}
|
|
27
26
|
return stars;
|
|
28
27
|
};
|
package/dist/components/row.d.ts
CHANGED
|
@@ -1,26 +1,41 @@
|
|
|
1
|
-
import type { AnsiComponent } from '../registry.js';
|
|
1
|
+
import type { AnsiComponent, DirectiveAttributes } from '../registry.js';
|
|
2
|
+
declare const TEXT_ALIGNS: readonly ["left", "center", "right"];
|
|
3
|
+
export type RowTextAlign = (typeof TEXT_ALIGNS)[number];
|
|
2
4
|
/** Below this width, cells stack vertically instead of sitting side by side — a named constant per the batch brief, not a magic number. */
|
|
3
5
|
export declare const ROW_COLUMN_THRESHOLD = 60;
|
|
6
|
+
/** The blank columns between two cells of a grid row. Shared by both emitters (the Ink flex `Box` and the flattened string). */
|
|
7
|
+
export declare const ROW_GUTTER = 1;
|
|
8
|
+
export interface RowLayout {
|
|
9
|
+
align: RowTextAlign;
|
|
10
|
+
columnCount: number;
|
|
11
|
+
colWidth: number;
|
|
12
|
+
stacked: boolean;
|
|
13
|
+
}
|
|
4
14
|
/**
|
|
5
|
-
* `:::row{cols=2|3|4 text=left|center|right} ... :::`
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
15
|
+
* `:::row{cols=2|3|4 text=left|center|right} ... :::`'s pure layout
|
|
16
|
+
* decision: how many columns, how wide each one is, and whether the row
|
|
17
|
+
* stacks. `render.tsx`'s walk calls this directly (rather than `row`'s own
|
|
18
|
+
* registered component) so it can build each cell's real Ink subtree at the
|
|
19
|
+
* correct width BEFORE assembling the row's flex `Box` — see `render.tsx`'s
|
|
20
|
+
* top comment and `registry.ts`'s doc comment on why this one directive is
|
|
21
|
+
* handled at the walk level. Kept here, not in `render.tsx`, so
|
|
22
|
+
* `components/contract-drift.test.ts`'s literal source scan of `row.tsx`
|
|
23
|
+
* still finds the `attributes.cols`/`attributes.text` reads.
|
|
14
24
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
+
* An absent/invalid `cols` auto-fits: every cell becomes one column, all in
|
|
26
|
+
* a single grid row. A `cols` value smaller than the cell count wraps into
|
|
27
|
+
* further grid rows, `cols` cells at a time. Below `ROW_COLUMN_THRESHOLD`
|
|
28
|
+
* columns, or with only one cell, cells stack vertically instead.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveRowLayout(attributes: DirectiveAttributes, cellCount: number, width: number): RowLayout;
|
|
31
|
+
/**
|
|
32
|
+
* `row`'s registered component: a passthrough. `render.tsx`'s walk builds
|
|
33
|
+
* the row's REAL flex-`Box` grid (or, for a `row` reached through STRING
|
|
34
|
+
* mode nested inside a self-drawing container, a flattened string) directly
|
|
35
|
+
* and hands it down as `children`, so the registered component's own job is
|
|
36
|
+
* just to return what it was given. Kept as a real registration (rather
|
|
37
|
+
* than omitted) so alias resolution, the contract-drift coverage test, and
|
|
38
|
+
* an unexpected direct invocation all still see a real `row` component.
|
|
25
39
|
*/
|
|
26
40
|
export declare const Row: AnsiComponent;
|
|
41
|
+
export {};
|
package/dist/components/row.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { columns } from '../box.js';
|
|
2
1
|
const COLS_VALUES = ['2', '3', '4'];
|
|
3
2
|
function isColsValue(value) {
|
|
4
3
|
return COLS_VALUES.includes(value);
|
|
@@ -9,59 +8,44 @@ function isTextAlign(value) {
|
|
|
9
8
|
}
|
|
10
9
|
/** Below this width, cells stack vertically instead of sitting side by side — a named constant per the batch brief, not a magic number. */
|
|
11
10
|
export const ROW_COLUMN_THRESHOLD = 60;
|
|
12
|
-
/**
|
|
13
|
-
const
|
|
11
|
+
/** The blank columns between two cells of a grid row. Shared by both emitters (the Ink flex `Box` and the flattened string). */
|
|
12
|
+
export const ROW_GUTTER = 1;
|
|
14
13
|
/**
|
|
15
|
-
* `:::row{cols=2|3|4 text=left|center|right} ... :::`
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
14
|
+
* `:::row{cols=2|3|4 text=left|center|right} ... :::`'s pure layout
|
|
15
|
+
* decision: how many columns, how wide each one is, and whether the row
|
|
16
|
+
* stacks. `render.tsx`'s walk calls this directly (rather than `row`'s own
|
|
17
|
+
* registered component) so it can build each cell's real Ink subtree at the
|
|
18
|
+
* correct width BEFORE assembling the row's flex `Box` — see `render.tsx`'s
|
|
19
|
+
* top comment and `registry.ts`'s doc comment on why this one directive is
|
|
20
|
+
* handled at the walk level. Kept here, not in `render.tsx`, so
|
|
21
|
+
* `components/contract-drift.test.ts`'s literal source scan of `row.tsx`
|
|
22
|
+
* still finds the `attributes.cols`/`attributes.text` reads.
|
|
24
23
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* that part's own `render({ width })` at the ACTUAL column width decided
|
|
30
|
-
* below, rather than rendered once at the row's full width and then
|
|
31
|
-
* re-wrapped into a narrower column. A cell containing a self-drawing box
|
|
32
|
-
* (a nested `card`/`table`) therefore draws that box at the real column
|
|
33
|
-
* width in the first place, instead of having an already-drawn frame
|
|
34
|
-
* mangled by a later re-wrap.
|
|
24
|
+
* An absent/invalid `cols` auto-fits: every cell becomes one column, all in
|
|
25
|
+
* a single grid row. A `cols` value smaller than the cell count wraps into
|
|
26
|
+
* further grid rows, `cols` cells at a time. Below `ROW_COLUMN_THRESHOLD`
|
|
27
|
+
* columns, or with only one cell, cells stack vertically instead.
|
|
35
28
|
*/
|
|
36
|
-
export
|
|
29
|
+
export function resolveRowLayout(attributes, cellCount, width) {
|
|
37
30
|
const rawTextAlign = attributes.text;
|
|
38
31
|
const align = rawTextAlign && isTextAlign(rawTextAlign) ? rawTextAlign : 'left';
|
|
39
|
-
const cellParts = children.parts;
|
|
40
|
-
if (cellParts.length === 0)
|
|
41
|
-
return '';
|
|
42
32
|
const rawCols = attributes.cols ?? '';
|
|
43
|
-
const requestedCols = isColsValue(rawCols)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
?
|
|
49
|
-
:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const rowCells = placedCells.slice(index, index + columnCount);
|
|
63
|
-
const widths = rowCells.map(() => colWidth);
|
|
64
|
-
gridRows.push(columns(rowCells, widths, GUTTER));
|
|
65
|
-
}
|
|
66
|
-
return gridRows.join('\n\n');
|
|
67
|
-
};
|
|
33
|
+
const requestedCols = isColsValue(rawCols) ? Number(rawCols) : cellCount;
|
|
34
|
+
const columnCount = Math.max(1, Math.min(requestedCols, Math.max(1, cellCount)));
|
|
35
|
+
const gutter = ROW_GUTTER;
|
|
36
|
+
const stacked = width < ROW_COLUMN_THRESHOLD || columnCount <= 1;
|
|
37
|
+
const colWidth = stacked
|
|
38
|
+
? width
|
|
39
|
+
: Math.max(1, Math.floor((width - gutter * (columnCount - 1)) / columnCount));
|
|
40
|
+
return { align, columnCount, colWidth, stacked };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* `row`'s registered component: a passthrough. `render.tsx`'s walk builds
|
|
44
|
+
* the row's REAL flex-`Box` grid (or, for a `row` reached through STRING
|
|
45
|
+
* mode nested inside a self-drawing container, a flattened string) directly
|
|
46
|
+
* and hands it down as `children`, so the registered component's own job is
|
|
47
|
+
* just to return what it was given. Kept as a real registration (rather
|
|
48
|
+
* than omitted) so alias resolution, the contract-drift coverage test, and
|
|
49
|
+
* an unexpected direct invocation all still see a real `row` component.
|
|
50
|
+
*/
|
|
51
|
+
export const Row = ({ children }) => children;
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import type { AnsiComponent } from '../registry.js';
|
|
2
2
|
/**
|
|
3
3
|
* `::stat{value=42 label="stars" trend=up}` — a big value plus label. Data
|
|
4
|
-
* binding (§8)
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* a blank line. Terminal form: a muted label line, then a bold value line
|
|
8
|
-
* (with `delta` appended, colored by `trend` when recognized). A failed or
|
|
9
|
-
* stale binding appends `failure-presentation.ts`'s quiet suffix to the
|
|
10
|
-
* value line — the terminal has no tooltip channel, so the reason has to
|
|
11
|
-
* reach the text itself (AGENTS.md's "clean is not silent").
|
|
4
|
+
* binding (§8): a bound number/string supplies `value`; a bound object may
|
|
5
|
+
* supply `value`/`label`/`delta`/`trend` — an explicit attribute always
|
|
6
|
+
* wins. Missing value renders `—` rather than a blank line.
|
|
12
7
|
*/
|
|
13
8
|
export declare const Stat: AnsiComponent;
|
package/dist/components/stat.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { formatValue } from '@markii/stdlib';
|
|
2
2
|
import { safeRead } from '../resolve.js';
|
|
3
3
|
import { dataStateSuffix, failureToken } from '../failure-presentation.js';
|
|
4
|
-
const EMPTY_VALUE = '—';
|
|
5
4
|
const TRENDS = ['up', 'down', 'flat'];
|
|
6
5
|
function isTrend(value) {
|
|
7
6
|
return TRENDS.includes(value);
|
|
@@ -35,16 +34,11 @@ function pick(attribute, fromData) {
|
|
|
35
34
|
}
|
|
36
35
|
/**
|
|
37
36
|
* `::stat{value=42 label="stars" trend=up}` — a big value plus label. Data
|
|
38
|
-
* binding (§8)
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* a blank line. Terminal form: a muted label line, then a bold value line
|
|
42
|
-
* (with `delta` appended, colored by `trend` when recognized). A failed or
|
|
43
|
-
* stale binding appends `failure-presentation.ts`'s quiet suffix to the
|
|
44
|
-
* value line — the terminal has no tooltip channel, so the reason has to
|
|
45
|
-
* reach the text itself (AGENTS.md's "clean is not silent").
|
|
37
|
+
* binding (§8): a bound number/string supplies `value`; a bound object may
|
|
38
|
+
* supply `value`/`label`/`delta`/`trend` — an explicit attribute always
|
|
39
|
+
* wins. Missing value renders `—` rather than a blank line.
|
|
46
40
|
*/
|
|
47
|
-
export const Stat = (attributes,
|
|
41
|
+
export const Stat = ({ attributes, ctx }) => {
|
|
48
42
|
const { data, dataStatus, dataFailureKind } = ctx;
|
|
49
43
|
const bound = safeRead(() => dataStatus === 'missing' || dataStatus === 'error'
|
|
50
44
|
? {}
|
|
@@ -59,7 +53,7 @@ export const Stat = (attributes, _children, ctx) => {
|
|
|
59
53
|
const lines = [];
|
|
60
54
|
if (label)
|
|
61
55
|
lines.push(ctx.style(ctx.text(label), '--mk-muted'));
|
|
62
|
-
let valueLine = ctx.bold(ctx.text(formattedValue ||
|
|
56
|
+
let valueLine = ctx.bold(ctx.text(formattedValue || ctx.glyphs.emptyValue));
|
|
63
57
|
if (delta) {
|
|
64
58
|
const deltaToken = trend === 'up'
|
|
65
59
|
? '--mk-success'
|
package/dist/components/tab.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AnsiComponent } from '../registry.js';
|
|
2
2
|
export declare const DEFAULT_TAB_LABEL = "Tab";
|
|
3
3
|
/**
|
|
4
4
|
* `:::tab{label="..."} ... :::` — one panel of a `tabs` component
|
|
5
|
-
* (`tabs.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
5
|
+
* (`tabs.tsx`). Rendered STANDALONE (outside a `tabs` parent, or when
|
|
6
|
+
* reached through `render.tsx`'s STRING-mode fallback), it reads its own
|
|
7
|
+
* `label` attribute directly and shows a bold heading above the panel body.
|
|
8
|
+
* When nested under a real `tabs` directive on the ELEMENT-mode path,
|
|
9
|
+
* `render.tsx`'s `extractTabPanelsElement` reads `label` itself and this
|
|
10
|
+
* component is never invoked for that child — `InteractiveTabs` draws the
|
|
11
|
+
* heading instead, since it needs to draw ALL headings together (the tab
|
|
12
|
+
* strip / stacked list), not one at a time.
|
|
12
13
|
*/
|
|
13
14
|
export declare const Tab: AnsiComponent;
|
package/dist/components/tab.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
+
import { childrenText } from '../registry.js';
|
|
1
2
|
export const DEFAULT_TAB_LABEL = 'Tab';
|
|
2
3
|
/**
|
|
3
4
|
* `:::tab{label="..."} ... :::` — one panel of a `tabs` component
|
|
4
|
-
* (`tabs.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* (`tabs.tsx`). Rendered STANDALONE (outside a `tabs` parent, or when
|
|
6
|
+
* reached through `render.tsx`'s STRING-mode fallback), it reads its own
|
|
7
|
+
* `label` attribute directly and shows a bold heading above the panel body.
|
|
8
|
+
* When nested under a real `tabs` directive on the ELEMENT-mode path,
|
|
9
|
+
* `render.tsx`'s `extractTabPanelsElement` reads `label` itself and this
|
|
10
|
+
* component is never invoked for that child — `InteractiveTabs` draws the
|
|
11
|
+
* heading instead, since it needs to draw ALL headings together (the tab
|
|
12
|
+
* strip / stacked list), not one at a time.
|
|
11
13
|
*/
|
|
12
|
-
export const Tab = (attributes, children, ctx) => {
|
|
14
|
+
export const Tab = ({ attributes, children, ctx }) => {
|
|
13
15
|
const label = attributes.label ?? DEFAULT_TAB_LABEL;
|
|
14
16
|
const heading = ctx.bold(ctx.text(label));
|
|
15
|
-
const
|
|
16
|
-
return
|
|
17
|
+
const body = childrenText(children);
|
|
18
|
+
return body ? `${heading}\n${body}` : heading;
|
|
17
19
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { GlyphSet } from '../glyphs.js';
|
|
1
2
|
/**
|
|
2
3
|
* The ONE box-drawing/width-negotiation routine every table this engine
|
|
3
4
|
* draws goes through: the data-bound `::table` component (`./table.ts`) and
|
|
@@ -29,6 +30,6 @@ export declare const MIN_COLUMN_WIDTH = 3;
|
|
|
29
30
|
* complete grid at most `width` columns wide when the natural content
|
|
30
31
|
* allows it, otherwise as narrow as `MIN_COLUMN_WIDTH` per column permits.
|
|
31
32
|
*/
|
|
32
|
-
export declare function drawTableGrid(header: readonly string[] | undefined, rows: readonly (readonly string[])[], width: number, boldFn?: (text: string) => string, align?: 'left' | 'center' | 'right'): string;
|
|
33
|
+
export declare function drawTableGrid(header: readonly string[] | undefined, rows: readonly (readonly string[])[], width: number, glyphs: GlyphSet, boldFn?: (text: string) => string, align?: 'left' | 'center' | 'right'): string;
|
|
33
34
|
/** The widest a `drawTableGrid` result's lines get, for a `selfLayout` component (`./table.ts`) that needs to know its own natural footprint (the `fit` width preset). */
|
|
34
35
|
export declare function measureTableGridWidth(header: readonly string[] | undefined, rows: readonly (readonly string[])[]): number;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { pad, wrap } from '../
|
|
2
|
-
import { measure } from '../measure.js';
|
|
1
|
+
import { padText as pad, wrapText as wrap, measureWidth as measure, } from '../text-grid.js';
|
|
3
2
|
/**
|
|
4
3
|
* The ONE box-drawing/width-negotiation routine every table this engine
|
|
5
4
|
* draws goes through: the data-bound `::table` component (`./table.ts`) and
|
|
@@ -76,7 +75,7 @@ function wrapRow(row, widths) {
|
|
|
76
75
|
const height = Math.max(1, ...lines.map((l) => l.length));
|
|
77
76
|
return { lines, height };
|
|
78
77
|
}
|
|
79
|
-
function drawRow(row, widths, boldRow, boldFn, align) {
|
|
78
|
+
function drawRow(row, widths, boldRow, boldFn, align, glyphs) {
|
|
80
79
|
const { lines, height } = wrapRow(row, widths);
|
|
81
80
|
const rowLines = [];
|
|
82
81
|
for (let line = 0; line < height; line += 1) {
|
|
@@ -84,12 +83,14 @@ function drawRow(row, widths, boldRow, boldFn, align) {
|
|
|
84
83
|
const text = pad(lines[c]?.[line] ?? '', w, align);
|
|
85
84
|
return boldRow ? boldFn(text) : text;
|
|
86
85
|
});
|
|
87
|
-
|
|
86
|
+
const bar = glyphs.solid.vertical;
|
|
87
|
+
rowLines.push(`${bar} ${cells.join(` ${bar} `)} ${bar}`);
|
|
88
88
|
}
|
|
89
89
|
return rowLines.join('\n');
|
|
90
90
|
}
|
|
91
|
-
function drawSeparator(widths, left, mid, right) {
|
|
92
|
-
|
|
91
|
+
function drawSeparator(widths, left, mid, right, glyphs) {
|
|
92
|
+
const rule = glyphs.solid.horizontal;
|
|
93
|
+
return `${left}${widths.map((w) => rule.repeat(w + CELL_PADDING)).join(mid)}${right}`;
|
|
93
94
|
}
|
|
94
95
|
/**
|
|
95
96
|
* Draws a box-drawn table. `header`, when given, is the bold first row,
|
|
@@ -101,19 +102,20 @@ function drawSeparator(widths, left, mid, right) {
|
|
|
101
102
|
* complete grid at most `width` columns wide when the natural content
|
|
102
103
|
* allows it, otherwise as narrow as `MIN_COLUMN_WIDTH` per column permits.
|
|
103
104
|
*/
|
|
104
|
-
export function drawTableGrid(header, rows, width, boldFn = (text) => text, align = 'left') {
|
|
105
|
+
export function drawTableGrid(header, rows, width, glyphs, boldFn = (text) => text, align = 'left') {
|
|
105
106
|
const columnCount = Math.max(header?.length ?? 0, ...rows.map((row) => row.length), 1);
|
|
106
107
|
const natural = naturalColumnWidths(header, rows, columnCount);
|
|
107
108
|
const widths = negotiateColumnWidths(natural, width);
|
|
108
109
|
const lines = [];
|
|
109
|
-
|
|
110
|
+
const g = glyphs;
|
|
111
|
+
lines.push(drawSeparator(widths, g.topLeft, g.teeDown, g.topRight, g));
|
|
110
112
|
if (header) {
|
|
111
|
-
lines.push(drawRow(header, widths, true, boldFn, align));
|
|
112
|
-
lines.push(drawSeparator(widths,
|
|
113
|
+
lines.push(drawRow(header, widths, true, boldFn, align, g));
|
|
114
|
+
lines.push(drawSeparator(widths, g.teeRight, g.cross, g.teeLeft, g));
|
|
113
115
|
}
|
|
114
116
|
for (const row of rows)
|
|
115
|
-
lines.push(drawRow(row, widths, false, boldFn, align));
|
|
116
|
-
lines.push(drawSeparator(widths,
|
|
117
|
+
lines.push(drawRow(row, widths, false, boldFn, align, g));
|
|
118
|
+
lines.push(drawSeparator(widths, g.bottomLeft, g.teeUp, g.bottomRight, g));
|
|
117
119
|
return lines.join('\n');
|
|
118
120
|
}
|
|
119
121
|
/** The widest a `drawTableGrid` result's lines get, for a `selfLayout` component (`./table.ts`) that needs to know its own natural footprint (the `fit` width preset). */
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import type { AnsiComponent } from '../registry.js';
|
|
2
2
|
/**
|
|
3
3
|
* `::table{data=users columns="name,role" limit=10}` — a data-bound table.
|
|
4
|
-
* Data binding (§8)
|
|
5
|
-
*
|
|
6
|
-
* `format`/`decimals` apply to numeric cells only; `text` aligns every
|
|
7
|
-
* cell's content within its column; `caption`, when given, is a bold line
|
|
8
|
-
* above the grid. A missing/stale/failed binding degrades to the same quiet
|
|
9
|
-
* "no data" line `chart`/`progress` use, with the failure suffix appended.
|
|
4
|
+
* Data binding (§8): `@markii/stdlib`'s `deriveTableShape` decides the
|
|
5
|
+
* layout from the bound value's own shape.
|
|
10
6
|
*
|
|
11
|
-
* Registered `selfLayout
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* the borders.
|
|
7
|
+
* Registered `selfLayout`: draws real box-drawing glyphs
|
|
8
|
+
* (`./table-grid.ts`), so it sizes its own grid off `ctx.layout` instead of
|
|
9
|
+
* letting a generic post-render narrow/pad corrupt the borders.
|
|
15
10
|
*/
|
|
16
11
|
export declare const Table: AnsiComponent;
|
package/dist/components/table.js
CHANGED
|
@@ -24,7 +24,7 @@ function parseLimit(raw) {
|
|
|
24
24
|
function limitRows(rows, limit) {
|
|
25
25
|
return limit === undefined ? rows : rows.slice(0, limit);
|
|
26
26
|
}
|
|
27
|
-
/** Renders one cell's display text: `format`/`decimals` apply only when the cell's raw value is itself numeric-like
|
|
27
|
+
/** Renders one cell's display text: `format`/`decimals` apply only when the cell's raw value is itself numeric-like. */
|
|
28
28
|
function renderCell(value, format, decimals) {
|
|
29
29
|
if (format && isNumericLike(value))
|
|
30
30
|
return formatValue(value, format, decimals);
|
|
@@ -36,19 +36,14 @@ function isTextAlign(value) {
|
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
38
|
* `::table{data=users columns="name,role" limit=10}` — a data-bound table.
|
|
39
|
-
* Data binding (§8)
|
|
40
|
-
*
|
|
41
|
-
* `format`/`decimals` apply to numeric cells only; `text` aligns every
|
|
42
|
-
* cell's content within its column; `caption`, when given, is a bold line
|
|
43
|
-
* above the grid. A missing/stale/failed binding degrades to the same quiet
|
|
44
|
-
* "no data" line `chart`/`progress` use, with the failure suffix appended.
|
|
39
|
+
* Data binding (§8): `@markii/stdlib`'s `deriveTableShape` decides the
|
|
40
|
+
* layout from the bound value's own shape.
|
|
45
41
|
*
|
|
46
|
-
* Registered `selfLayout
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* the borders.
|
|
42
|
+
* Registered `selfLayout`: draws real box-drawing glyphs
|
|
43
|
+
* (`./table-grid.ts`), so it sizes its own grid off `ctx.layout` instead of
|
|
44
|
+
* letting a generic post-render narrow/pad corrupt the borders.
|
|
50
45
|
*/
|
|
51
|
-
export const Table = (attributes,
|
|
46
|
+
export const Table = ({ attributes, ctx }) => {
|
|
52
47
|
const { data, dataStatus, dataFailureKind } = ctx;
|
|
53
48
|
const columnsOverride = parseColumns(attributes.columns);
|
|
54
49
|
const limit = parseLimit(attributes.limit);
|
|
@@ -94,7 +89,7 @@ export const Table = (attributes, _children, ctx) => {
|
|
|
94
89
|
}
|
|
95
90
|
const naturalWidth = measureTableGridWidth(header, rows);
|
|
96
91
|
const boxWidth = selfLayoutWidth(ctx.layout, ctx.width, naturalWidth);
|
|
97
|
-
const grid = drawTableGrid(header, rows, boxWidth, (text) => ctx.bold(text), align);
|
|
92
|
+
const grid = drawTableGrid(header, rows, boxWidth, ctx.glyphs, (text) => ctx.bold(text), align);
|
|
98
93
|
const body = `${grid}${styledSuffix}`;
|
|
99
94
|
const withCaption = captionLine ? `${captionLine}\n${body}` : body;
|
|
100
95
|
return selfLayoutAlign(withCaption, ctx.layout, ctx.width);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type AnsiComponent } from '../registry.js';
|
|
2
|
+
/**
|
|
3
|
+
* The STRING-mode `tabs` registration — the fallback rendering used only
|
|
4
|
+
* when a `tabs` directive is reached through `render.tsx`'s STRING mode
|
|
5
|
+
* (nested inside a self-drawing container) AND, unlike `row`, not
|
|
6
|
+
* reachable through the panel-extraction path there either (both of
|
|
7
|
+
* `render.tsx`'s STRING and ELEMENT dispatchers for `tabs` build panels
|
|
8
|
+
* directly from the hast tree via `extractTabPanels`/`extractTabPanelsElement`
|
|
9
|
+
* rather than calling this component, since only the hast tree — not an
|
|
10
|
+
* already-flattened string — can tell each `tab` child's own `label` apart
|
|
11
|
+
* from its neighbor without a fragile heuristic). This registration exists
|
|
12
|
+
* so the registry always has a real `tabs` component (alias resolution,
|
|
13
|
+
* the contract-drift coverage test, and a caller invoking it directly all
|
|
14
|
+
* see one), using the same blank-line-boundary heuristic the pre-Ink engine
|
|
15
|
+
* used when it has nothing but a flattened string to work with.
|
|
16
|
+
*/
|
|
17
|
+
export declare const Tabs: AnsiComponent;
|