@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/dist/components/chart.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { measure } from '../measure.js';
|
|
1
|
+
import { padText, measureWidth } from '../text-grid.js';
|
|
3
2
|
import { safeRead } from '../resolve.js';
|
|
4
3
|
import { dataStateSuffix, failureToken } from '../failure-presentation.js';
|
|
5
4
|
const CHART_KINDS = ['line', 'bar'];
|
|
@@ -30,7 +29,7 @@ function coercePoint(entry) {
|
|
|
30
29
|
}
|
|
31
30
|
return undefined;
|
|
32
31
|
}
|
|
33
|
-
/** Builds the numeric point series to plot, a bound `data` array taking priority over the static `values=` attribute.
|
|
32
|
+
/** Builds the numeric point series to plot, a bound `data` array taking priority over the static `values=` attribute. */
|
|
34
33
|
function resolvePoints(data, dataStatus, rawValues) {
|
|
35
34
|
if (dataStatus !== 'missing' &&
|
|
36
35
|
dataStatus !== 'error' &&
|
|
@@ -58,26 +57,25 @@ function resolvePoints(data, dataStatus, rawValues) {
|
|
|
58
57
|
}
|
|
59
58
|
return [];
|
|
60
59
|
}
|
|
61
|
-
/** `▁▂▃▄▅▆▇█`, the eight-step block-height ramp a line chart's sparkline row is built from. */
|
|
62
|
-
const SPARKLINE_CHARS = '▁▂▃▄▅▆▇█';
|
|
63
60
|
function formatPoint(value) {
|
|
64
61
|
return Number.isFinite(value)
|
|
65
62
|
? String(Math.round(value * 100) / 100)
|
|
66
63
|
: String(value);
|
|
67
64
|
}
|
|
68
|
-
|
|
65
|
+
/** One sparkline row over `ramp`, the render's eight-step `GlyphSet.sparkline`. */
|
|
66
|
+
function sparkline(points, ramp) {
|
|
69
67
|
const min = Math.min(...points);
|
|
70
68
|
const max = Math.max(...points);
|
|
71
69
|
const range = max - min;
|
|
72
70
|
return points
|
|
73
71
|
.map((point) => {
|
|
74
72
|
const normalized = range > 0 ? (point - min) / range : 0.5;
|
|
75
|
-
const index = Math.min(
|
|
76
|
-
return
|
|
73
|
+
const index = Math.min(ramp.length - 1, Math.max(0, Math.round(normalized * (ramp.length - 1))));
|
|
74
|
+
return ramp[index];
|
|
77
75
|
})
|
|
78
76
|
.join('');
|
|
79
77
|
}
|
|
80
|
-
/**
|
|
78
|
+
/** A bar's length for `point`, scaled against the series' largest magnitude and capped at `budget` columns. */
|
|
81
79
|
function barLength(point, maxMagnitude, budget) {
|
|
82
80
|
if (maxMagnitude <= 0)
|
|
83
81
|
return 0;
|
|
@@ -85,18 +83,12 @@ function barLength(point, maxMagnitude, budget) {
|
|
|
85
83
|
}
|
|
86
84
|
/**
|
|
87
85
|
* `::chart{kind=line|bar values="1,3,2,5"}` — a dependency-free, hand-rolled
|
|
88
|
-
* chart. Data binding (§8)
|
|
89
|
-
*
|
|
90
|
-
* `
|
|
91
|
-
*
|
|
92
|
-
* a small neutral "no data" line rather than a broken chart.
|
|
93
|
-
*
|
|
94
|
-
* Terminal form: a `line` chart is a single sparkline row (`▁▂▃▄▅▆▇█`),
|
|
95
|
-
* flanked by its minimum and maximum as plain numbers. A `bar` chart is one
|
|
96
|
-
* horizontal `█` bar per point, its own value as the row's label, labels
|
|
97
|
-
* right-aligned to the widest one so every bar starts at the same column.
|
|
86
|
+
* chart. Data binding (§8): a bound `data` array of numbers (or `{value}`
|
|
87
|
+
* objects) takes priority over the static `values=` attribute. Terminal
|
|
88
|
+
* form: a `line` chart is a single sparkline row, a `bar` chart is one
|
|
89
|
+
* horizontal bar per point.
|
|
98
90
|
*/
|
|
99
|
-
export const Chart = (attributes,
|
|
91
|
+
export const Chart = ({ attributes, ctx }) => {
|
|
100
92
|
const { data, dataStatus, dataFailureKind } = ctx;
|
|
101
93
|
const rawKind = attributes.kind ?? DEFAULT_KIND;
|
|
102
94
|
const kind = isChartKind(rawKind) ? rawKind : DEFAULT_KIND;
|
|
@@ -115,15 +107,15 @@ export const Chart = (attributes, _children, ctx) => {
|
|
|
115
107
|
if (kind === 'line') {
|
|
116
108
|
const min = ctx.dim(formatPoint(Math.min(...points)));
|
|
117
109
|
const max = ctx.dim(formatPoint(Math.max(...points)));
|
|
118
|
-
return `${min} ${sparkline(points)} ${max}${styledSuffix}`;
|
|
110
|
+
return `${min} ${sparkline(points, ctx.glyphs.sparkline)} ${max}${styledSuffix}`;
|
|
119
111
|
}
|
|
120
112
|
const labels = points.map((point) => formatPoint(point));
|
|
121
|
-
const labelWidth = Math.max(...labels.map((label) =>
|
|
113
|
+
const labelWidth = Math.max(...labels.map((label) => measureWidth(label)));
|
|
122
114
|
const budget = Math.max(1, ctx.width - labelWidth - 1);
|
|
123
115
|
const maxMagnitude = Math.max(...points.map((point) => Math.abs(point)));
|
|
124
116
|
const lines = points.map((point, index) => {
|
|
125
|
-
const bar =
|
|
126
|
-
return `${
|
|
117
|
+
const bar = ctx.glyphs.barFilled.repeat(barLength(point, maxMagnitude, budget));
|
|
118
|
+
return `${padText(labels[index], labelWidth, 'right')} ${bar}`;
|
|
127
119
|
});
|
|
128
120
|
return `${lines.join('\n')}${styledSuffix}`;
|
|
129
121
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type AnsiComponent } from '../registry.js';
|
|
2
|
+
/** Columns the indent prefix (" ") consumes from the enclosing width. */
|
|
3
|
+
export declare const DETAILS_INDENT_WIDTH = 2;
|
|
4
|
+
/**
|
|
5
|
+
* The inner budget `details`'s body renders at, given only the outer width
|
|
6
|
+
* — never the body itself. Mirrors `card.ts`'s `resolveCardInnerWidth`:
|
|
7
|
+
* `render.tsx`'s walk calls this BEFORE building children (for both the
|
|
8
|
+
* STRING-mode fallback below and the real interactive element builder in
|
|
9
|
+
* `details.tsx`), so the body — and anything self-drawing nested inside it
|
|
10
|
+
* — is built exactly once, at the correct width, never re-wrapped
|
|
11
|
+
* afterward.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveDetailsInnerWidth(outerWidth: number): number;
|
|
14
|
+
/**
|
|
15
|
+
* The STRING-mode `details` registration (used by `render.tsx`'s registry
|
|
16
|
+
* lookups, and as the fallback rendering when a `details` directive is
|
|
17
|
+
* reached through STRING mode — nested inside a self-drawing container's
|
|
18
|
+
* own body, see `render.tsx`'s top comment). The real Ink/interactive
|
|
19
|
+
* version `render.tsx` builds directly for a `details` reached through
|
|
20
|
+
* ELEMENT mode is `InteractiveDetails` in `./details.tsx`; the two share no
|
|
21
|
+
* code because this one can only ever show the body (a plain string has no
|
|
22
|
+
* expand/collapse affordance), exactly like the pre-Ink engine. `children`
|
|
23
|
+
* arrives ALREADY built at exactly `resolveDetailsInnerWidth`'s width —
|
|
24
|
+
* this component never re-wraps.
|
|
25
|
+
*/
|
|
26
|
+
export declare const Details: AnsiComponent;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { indentBlock } from '../text-grid.js';
|
|
2
|
+
import { childrenText } from '../registry.js';
|
|
3
|
+
const DEFAULT_TITLE = 'Details';
|
|
4
|
+
/** Columns the indent prefix (" ") consumes from the enclosing width. */
|
|
5
|
+
export const DETAILS_INDENT_WIDTH = 2;
|
|
6
|
+
/**
|
|
7
|
+
* The inner budget `details`'s body renders at, given only the outer width
|
|
8
|
+
* — never the body itself. Mirrors `card.ts`'s `resolveCardInnerWidth`:
|
|
9
|
+
* `render.tsx`'s walk calls this BEFORE building children (for both the
|
|
10
|
+
* STRING-mode fallback below and the real interactive element builder in
|
|
11
|
+
* `details.tsx`), so the body — and anything self-drawing nested inside it
|
|
12
|
+
* — is built exactly once, at the correct width, never re-wrapped
|
|
13
|
+
* afterward.
|
|
14
|
+
*/
|
|
15
|
+
export function resolveDetailsInnerWidth(outerWidth) {
|
|
16
|
+
return Math.max(1, outerWidth - DETAILS_INDENT_WIDTH);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The STRING-mode `details` registration (used by `render.tsx`'s registry
|
|
20
|
+
* lookups, and as the fallback rendering when a `details` directive is
|
|
21
|
+
* reached through STRING mode — nested inside a self-drawing container's
|
|
22
|
+
* own body, see `render.tsx`'s top comment). The real Ink/interactive
|
|
23
|
+
* version `render.tsx` builds directly for a `details` reached through
|
|
24
|
+
* ELEMENT mode is `InteractiveDetails` in `./details.tsx`; the two share no
|
|
25
|
+
* code because this one can only ever show the body (a plain string has no
|
|
26
|
+
* expand/collapse affordance), exactly like the pre-Ink engine. `children`
|
|
27
|
+
* arrives ALREADY built at exactly `resolveDetailsInnerWidth`'s width —
|
|
28
|
+
* this component never re-wraps.
|
|
29
|
+
*/
|
|
30
|
+
export const Details = ({ attributes, children, ctx }) => {
|
|
31
|
+
const title = attributes.title ?? DEFAULT_TITLE;
|
|
32
|
+
const open = Object.hasOwn(attributes, 'open');
|
|
33
|
+
const glyph = open ? ctx.glyphs.foldOpen : ctx.glyphs.foldClosed;
|
|
34
|
+
const marker = ctx.dim(`${glyph} ${ctx.bold(ctx.text(title))} (collapsible section, shown expanded)`);
|
|
35
|
+
const body = childrenText(children);
|
|
36
|
+
if (!body)
|
|
37
|
+
return marker;
|
|
38
|
+
return `${marker}\n${indentBlock(body, ' ')}`;
|
|
39
|
+
};
|
|
@@ -1,12 +1,34 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GlyphSet } from '../glyphs.js';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
import type { ColorLevel } from '../ansi.js';
|
|
2
4
|
/**
|
|
3
|
-
* `:::details{title="..." open} ... :::` — a collapsible disclosure
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* `:::details{title="..." open} ... :::` — a collapsible disclosure, built
|
|
6
|
+
* as a real Ink element (`render.tsx`'s `renderDetailsElement` calls this
|
|
7
|
+
* directly, the same way it calls `InteractiveTabs` for `tabs` — see
|
|
8
|
+
* `registry.ts`'s and `render.tsx`'s doc comments).
|
|
9
|
+
*
|
|
10
|
+
* Non-interactive (the default, and the render-once path): ALWAYS shows the
|
|
11
|
+
* body, exactly like the pre-Ink engine — a static render has no
|
|
12
|
+
* expand/collapse affordance. The bare `open` attribute only changes the
|
|
13
|
+
* marker glyph (`▾` for `open`, `▸` for folded-by-default), a quiet hint at
|
|
14
|
+
* the note's own authored default.
|
|
15
|
+
*
|
|
16
|
+
* Interactive: starts CLOSED unless `open` was given; Enter toggles it when
|
|
17
|
+
* this `details` instance is focused (`useIsFocused`). When focused, the
|
|
18
|
+
* summary line also carries a dim hint (`enter to open`/`enter to close`,
|
|
19
|
+
* matching the current state) and the whole line is drawn in the accent
|
|
20
|
+
* color with a leading `› ` glyph (`useFocusMarker`), so focus is visible
|
|
21
|
+
* even on a theme with no perceptible accent color; unfocused, the line
|
|
22
|
+
* gets two leading spaces instead, so nothing shifts horizontally when
|
|
23
|
+
* focus moves onto or off of this block.
|
|
11
24
|
*/
|
|
12
|
-
export
|
|
25
|
+
export interface InteractiveDetailsProps {
|
|
26
|
+
title: string;
|
|
27
|
+
body: ReactNode;
|
|
28
|
+
defaultOpen: boolean;
|
|
29
|
+
color: ColorLevel;
|
|
30
|
+
glyphs: GlyphSet;
|
|
31
|
+
interactive: boolean;
|
|
32
|
+
focusId: number | undefined;
|
|
33
|
+
}
|
|
34
|
+
export declare function InteractiveDetails({ title, body, defaultOpen, color, glyphs, interactive, focusId, }: InteractiveDetailsProps): ReactNode;
|
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
};
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { bold, dim } from '../ansi.js';
|
|
5
|
+
import { useFocusAnchor, useFocusMarker, useIsFocused, } from '../interactive.js';
|
|
6
|
+
export function InteractiveDetails({ title, body, defaultOpen, color, glyphs, interactive, focusId, }) {
|
|
7
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
8
|
+
const focused = useIsFocused(focusId);
|
|
9
|
+
const anchor = useFocusAnchor(focusId);
|
|
10
|
+
useInput((_input, key) => {
|
|
11
|
+
if (key.return)
|
|
12
|
+
setOpen((current) => !current);
|
|
13
|
+
}, { isActive: interactive && focused });
|
|
14
|
+
// Every hook this component calls runs BEFORE the non-interactive early
|
|
15
|
+
// return below, so the hook order is identical in both modes. The summary
|
|
16
|
+
// line is cheap to build and unused in the non-interactive branch; that
|
|
17
|
+
// waste is the price of never making a hook call conditional.
|
|
18
|
+
const openGlyph = open ? glyphs.foldOpen : glyphs.foldClosed;
|
|
19
|
+
const hint = focused ? (open ? ' enter to close' : ' enter to open') : '';
|
|
20
|
+
const summaryLine = dim(`${openGlyph} ${bold(title, color)}${hint}`, color);
|
|
21
|
+
const focusGlyph = focused ? glyphs.focus : ' ';
|
|
22
|
+
const headingLine = useFocusMarker(`${focusGlyph}${summaryLine}`, focusId);
|
|
23
|
+
if (!interactive) {
|
|
24
|
+
const glyph = defaultOpen ? glyphs.foldOpen : glyphs.foldClosed;
|
|
25
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: dim(`${glyph} ${bold(title, color)} (collapsible section, shown expanded)`, color) }), _jsx(Box, { marginLeft: 2, flexDirection: "column", children: body })] }));
|
|
26
|
+
}
|
|
27
|
+
return (_jsxs(Box, { ref: anchor, flexDirection: "column", children: [_jsx(Text, { children: headingLine }), open && (_jsx(Box, { marginLeft: 2, flexDirection: "column", children: body }))] }));
|
|
28
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { measureWidth, padText, ruleLine } from '../text-grid.js';
|
|
2
2
|
import { selfLayoutAlign, selfLayoutWidth } from '../layout.js';
|
|
3
3
|
const DIVIDER_VARIANTS = [
|
|
4
4
|
'line',
|
|
@@ -12,15 +12,13 @@ const LABEL_ALIGNS = ['left', 'center', 'right'];
|
|
|
12
12
|
function isDividerLabelAlign(value) {
|
|
13
13
|
return LABEL_ALIGNS.includes(value);
|
|
14
14
|
}
|
|
15
|
-
const ORNAMENT_GLYPH = '❖';
|
|
16
15
|
/** The rule character each non-ornament variant draws with. */
|
|
17
|
-
const RULE_CHAR = { line: '─', dots: '·' };
|
|
18
16
|
/** How much of a labeled rule's rule fill sits on the SHORT side when the label is pushed left/right (the rest fills the long side). */
|
|
19
17
|
const SHORT_SIDE_RULE = 2;
|
|
20
18
|
/** Builds a full-`width` rule with `label` woven in at `align`, e.g. `── Part 2 ─────────`. */
|
|
21
19
|
function labeledRule(ruleChar, label, width, align) {
|
|
22
20
|
const labelText = ` ${label} `;
|
|
23
|
-
const remaining = Math.max(0, width -
|
|
21
|
+
const remaining = Math.max(0, width - measureWidth(labelText));
|
|
24
22
|
let left;
|
|
25
23
|
if (align === 'left')
|
|
26
24
|
left = Math.min(SHORT_SIDE_RULE, remaining);
|
|
@@ -42,15 +40,13 @@ function labeledRule(ruleChar, label, width, align) {
|
|
|
42
40
|
* within the width. Dimmed, matching the plain thematic-break rendering.
|
|
43
41
|
*
|
|
44
42
|
* Registered `selfLayout` (`registry.ts`'s `AnsiRegistryEntry.selfLayout`):
|
|
45
|
-
* a full-width rule is ONE long run with no spaces in it, so
|
|
43
|
+
* a full-width rule is ONE long run with no spaces in it, so the generic
|
|
46
44
|
* post-render `applyLayout` narrowing would treat the whole rule as a
|
|
47
|
-
* single over-long "word" and hard-break it
|
|
48
|
-
*
|
|
49
|
-
* rule — this component instead reads `ctx.layout` and draws its own rule
|
|
50
|
-
* at the resolved width from the start, exactly like `card`/`callout`.
|
|
45
|
+
* single over-long "word" and hard-break it — this component instead reads
|
|
46
|
+
* `ctx.layout` and draws its own rule at the resolved width from the start.
|
|
51
47
|
*/
|
|
52
48
|
const FIT_DEFAULT_WIDTH = 10;
|
|
53
|
-
export const Divider = (attributes,
|
|
49
|
+
export const Divider = ({ attributes, ctx }) => {
|
|
54
50
|
const rawVariant = attributes.variant ?? 'line';
|
|
55
51
|
const variant = isDividerVariant(rawVariant)
|
|
56
52
|
? rawVariant
|
|
@@ -61,20 +57,20 @@ export const Divider = (attributes, _children, ctx) => {
|
|
|
61
57
|
const labelAlign = rawLabelAlign && isDividerLabelAlign(rawLabelAlign)
|
|
62
58
|
? rawLabelAlign
|
|
63
59
|
: 'center';
|
|
64
|
-
const naturalWidth = label ?
|
|
60
|
+
const naturalWidth = label ? measureWidth(label) + 4 : FIT_DEFAULT_WIDTH;
|
|
65
61
|
const width = selfLayoutWidth(ctx.layout, ctx.width, naturalWidth);
|
|
66
62
|
let line;
|
|
67
63
|
if (variant === 'ornament') {
|
|
68
64
|
const inner = label
|
|
69
|
-
? `${
|
|
70
|
-
:
|
|
71
|
-
line =
|
|
65
|
+
? `${ctx.glyphs.ornament} ${label} ${ctx.glyphs.ornament}`
|
|
66
|
+
: ctx.glyphs.ornament;
|
|
67
|
+
line = padText(inner, width, labelAlign);
|
|
72
68
|
}
|
|
73
69
|
else {
|
|
74
|
-
const ruleChar =
|
|
70
|
+
const ruleChar = variant === 'dots' ? ctx.glyphs.ruleDots : ctx.glyphs.rule;
|
|
75
71
|
line = label
|
|
76
72
|
? labeledRule(ruleChar, label, width, labelAlign)
|
|
77
|
-
:
|
|
73
|
+
: ruleLine(width, ruleChar);
|
|
78
74
|
}
|
|
79
75
|
return selfLayoutAlign(ctx.dim(line), ctx.layout, ctx.width);
|
|
80
76
|
};
|
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AnsiComponent } from '../registry.js';
|
|
2
2
|
/**
|
|
3
3
|
* `:::figure{src="..." alt="..."} caption markdown :::` — an image with a
|
|
4
|
-
* rich (markdown) caption. `src` is required; a missing `src` shows only
|
|
5
|
-
* caption
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Security: `src` bypasses `@markii/core`'s hast-level URL sanitizer (it is
|
|
9
|
-
* a directive attribute, not a markdown image), so this component closes
|
|
10
|
-
* that gap itself with `@markii/core`'s `isSafeUrl` — the same allowlist the
|
|
11
|
-
* sanitizer uses — and drops the image line entirely on a refusal, replacing
|
|
12
|
-
* it with a short labeled marker (a terminal has no tooltip to carry the
|
|
13
|
-
* full sentence out of the text flow) plus the full sentence reported to
|
|
14
|
-
* `onDiagnostic`; matches `@markii/html`'s `Figure` in behavior, not in the
|
|
15
|
-
* inline wording.
|
|
16
|
-
*
|
|
17
|
-
* Terminal form: alt text, then the (possibly host-resolved) src, then the
|
|
18
|
-
* caption, each on its own line, dimmed except the caption itself.
|
|
4
|
+
* rich (markdown) caption. `src` is required; a missing `src` shows only
|
|
5
|
+
* the caption. Security: `src` bypasses `@markii/core`'s hast-level URL
|
|
6
|
+
* sanitizer (it is a directive attribute, not a markdown image), so this
|
|
7
|
+
* component closes that gap itself with `isSafeUrl`.
|
|
19
8
|
*/
|
|
20
9
|
export declare const Figure: AnsiComponent;
|
|
@@ -2,27 +2,17 @@ import { isSafeUrl } from '@markii/core';
|
|
|
2
2
|
import { reportDiagnostic } from '@markii/stdlib';
|
|
3
3
|
import { resolveImageAttribute } from '../image-resolve.js';
|
|
4
4
|
import { unsafeImageSrcLabel, unsafeImageSrcTitle, } from '../failure-presentation.js';
|
|
5
|
+
import { childrenText } from '../registry.js';
|
|
5
6
|
const DEFAULT_ALT = '';
|
|
6
7
|
const DIRECTIVE_NAME = 'figure';
|
|
7
8
|
/**
|
|
8
9
|
* `:::figure{src="..." alt="..."} caption markdown :::` — an image with a
|
|
9
|
-
* rich (markdown) caption. `src` is required; a missing `src` shows only
|
|
10
|
-
* caption
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Security: `src` bypasses `@markii/core`'s hast-level URL sanitizer (it is
|
|
14
|
-
* a directive attribute, not a markdown image), so this component closes
|
|
15
|
-
* that gap itself with `@markii/core`'s `isSafeUrl` — the same allowlist the
|
|
16
|
-
* sanitizer uses — and drops the image line entirely on a refusal, replacing
|
|
17
|
-
* it with a short labeled marker (a terminal has no tooltip to carry the
|
|
18
|
-
* full sentence out of the text flow) plus the full sentence reported to
|
|
19
|
-
* `onDiagnostic`; matches `@markii/html`'s `Figure` in behavior, not in the
|
|
20
|
-
* inline wording.
|
|
21
|
-
*
|
|
22
|
-
* Terminal form: alt text, then the (possibly host-resolved) src, then the
|
|
23
|
-
* caption, each on its own line, dimmed except the caption itself.
|
|
10
|
+
* rich (markdown) caption. `src` is required; a missing `src` shows only
|
|
11
|
+
* the caption. Security: `src` bypasses `@markii/core`'s hast-level URL
|
|
12
|
+
* sanitizer (it is a directive attribute, not a markdown image), so this
|
|
13
|
+
* component closes that gap itself with `isSafeUrl`.
|
|
24
14
|
*/
|
|
25
|
-
export const Figure = (attributes, children, ctx) => {
|
|
15
|
+
export const Figure = ({ attributes, children, ctx }) => {
|
|
26
16
|
const rawSrc = attributes.src ?? null;
|
|
27
17
|
const alt = attributes.alt ?? DEFAULT_ALT;
|
|
28
18
|
const refused = Boolean(rawSrc) && !isSafeUrl(rawSrc);
|
|
@@ -46,8 +36,8 @@ export const Figure = (attributes, children, ctx) => {
|
|
|
46
36
|
lines.push(ctx.dim(src));
|
|
47
37
|
}
|
|
48
38
|
}
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
51
|
-
lines.push(
|
|
39
|
+
const body = childrenText(children);
|
|
40
|
+
if (body)
|
|
41
|
+
lines.push(body);
|
|
52
42
|
return lines.join('\n');
|
|
53
43
|
};
|
|
@@ -6,7 +6,8 @@ export type { CalloutType } from './callout.js';
|
|
|
6
6
|
export { Card } from './card.js';
|
|
7
7
|
export { Cell } from './cell.js';
|
|
8
8
|
export { Chart } from './chart.js';
|
|
9
|
-
export { Details } from './details.js';
|
|
9
|
+
export { Details } from './details-string.js';
|
|
10
|
+
export { InteractiveDetails } from './details.js';
|
|
10
11
|
export { Divider } from './divider.js';
|
|
11
12
|
export type { DividerVariant } from './divider.js';
|
|
12
13
|
export { Figure } from './figure.js';
|
|
@@ -15,20 +16,23 @@ export { createLayoutWrapper, layoutWrapperPresetAxis, LAYOUT_WRAPPER_PRESETS, }
|
|
|
15
16
|
export type { LayoutWrapperPreset } from './layout-wrapper.js';
|
|
16
17
|
export { Progress } from './progress.js';
|
|
17
18
|
export { Rating } from './rating.js';
|
|
18
|
-
export { Row, ROW_COLUMN_THRESHOLD } from './row.js';
|
|
19
|
+
export { Row, ROW_COLUMN_THRESHOLD, resolveRowLayout } from './row.js';
|
|
19
20
|
export { Stat } from './stat.js';
|
|
20
21
|
export { Tab, DEFAULT_TAB_LABEL } from './tab.js';
|
|
21
|
-
export { Tabs } from './tabs.js';
|
|
22
|
+
export { Tabs } from './tabs-string.js';
|
|
23
|
+
export { InteractiveTabs } from './tabs.js';
|
|
22
24
|
export { Table } from './table.js';
|
|
23
25
|
export { drawTableGrid, measureTableGridWidth, MIN_COLUMN_WIDTH, } from './table-grid.js';
|
|
24
26
|
/**
|
|
25
|
-
* The built-in standard components, pre-registered under their names
|
|
26
|
-
*
|
|
27
|
-
* `defaultRegistry`
|
|
28
|
-
* `
|
|
29
|
-
* `AnsiRegistryEntry.selfLayout`) because they draw a single long
|
|
27
|
+
* The built-in standard components, pre-registered under their names: the
|
|
28
|
+
* same 23 names as `@markii/html`'s `defaultHtmlRegistry` and
|
|
29
|
+
* `@markii/react`'s `defaultRegistry`. `card`, `callout`, `divider`, and
|
|
30
|
+
* `table` are marked `selfLayout` because they draw a single long
|
|
30
31
|
* frame/bar/rule/grid a generic post-render `applyLayout` narrow/pad would
|
|
31
|
-
* corrupt or hard-break mid-glyph
|
|
32
|
-
*
|
|
32
|
+
* corrupt or hard-break mid-glyph. `row`, `tabs`, and `details` are the
|
|
33
|
+
* three directives `render.tsx` gives real Ink layout/state to directly
|
|
34
|
+
* (see `registry.ts`'s and `render.tsx`'s doc comments); their REGISTERED
|
|
35
|
+
* `component` here is the STRING-mode fallback, reached only when one of
|
|
36
|
+
* them is nested inside a self-drawing container's own body.
|
|
33
37
|
*/
|
|
34
38
|
export declare const defaultAnsiRegistry: AnsiRegistry;
|
package/dist/components/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Callout } from './callout.js';
|
|
|
5
5
|
import { Card } from './card.js';
|
|
6
6
|
import { Cell } from './cell.js';
|
|
7
7
|
import { Chart } from './chart.js';
|
|
8
|
-
import { Details } from './details.js';
|
|
8
|
+
import { Details } from './details-string.js';
|
|
9
9
|
import { Divider } from './divider.js';
|
|
10
10
|
import { Figure } from './figure.js';
|
|
11
11
|
import { Kbd } from './kbd.js';
|
|
@@ -15,31 +15,33 @@ import { Rating } from './rating.js';
|
|
|
15
15
|
import { Row } from './row.js';
|
|
16
16
|
import { Stat } from './stat.js';
|
|
17
17
|
import { Tab } from './tab.js';
|
|
18
|
-
import { Tabs } from './tabs.js';
|
|
18
|
+
import { Tabs } from './tabs-string.js';
|
|
19
19
|
import { Table } from './table.js';
|
|
20
20
|
export { Badge } from './badge.js';
|
|
21
21
|
export { Callout } from './callout.js';
|
|
22
22
|
export { Card } from './card.js';
|
|
23
23
|
export { Cell } from './cell.js';
|
|
24
24
|
export { Chart } from './chart.js';
|
|
25
|
-
export { Details } from './details.js';
|
|
25
|
+
export { Details } from './details-string.js';
|
|
26
|
+
export { InteractiveDetails } from './details.js';
|
|
26
27
|
export { Divider } from './divider.js';
|
|
27
28
|
export { Figure } from './figure.js';
|
|
28
29
|
export { Kbd } from './kbd.js';
|
|
29
30
|
export { createLayoutWrapper, layoutWrapperPresetAxis, LAYOUT_WRAPPER_PRESETS, } from './layout-wrapper.js';
|
|
30
31
|
export { Progress } from './progress.js';
|
|
31
32
|
export { Rating } from './rating.js';
|
|
32
|
-
export { Row, ROW_COLUMN_THRESHOLD } from './row.js';
|
|
33
|
+
export { Row, ROW_COLUMN_THRESHOLD, resolveRowLayout } from './row.js';
|
|
33
34
|
export { Stat } from './stat.js';
|
|
34
35
|
export { Tab, DEFAULT_TAB_LABEL } from './tab.js';
|
|
35
|
-
export { Tabs } from './tabs.js';
|
|
36
|
+
export { Tabs } from './tabs-string.js';
|
|
37
|
+
export { InteractiveTabs } from './tabs.js';
|
|
36
38
|
export { Table } from './table.js';
|
|
37
39
|
export { drawTableGrid, measureTableGridWidth, MIN_COLUMN_WIDTH, } from './table-grid.js';
|
|
38
40
|
/**
|
|
39
41
|
* Derives a registry entry's `inline` flag from `@markii/stdlib`'s standard
|
|
40
|
-
* component contract for `name
|
|
41
|
-
*
|
|
42
|
-
*
|
|
42
|
+
* component contract for `name`: `kind: 'inline'` -> `inline: true`,
|
|
43
|
+
* otherwise `false`. Falls back to `false` if `name` has no standard
|
|
44
|
+
* contract.
|
|
43
45
|
*/
|
|
44
46
|
function inlineFromContract(name) {
|
|
45
47
|
return getContract(name)?.kind === 'inline';
|
|
@@ -47,7 +49,6 @@ function inlineFromContract(name) {
|
|
|
47
49
|
/**
|
|
48
50
|
* One layout-wrapper registration: the shared wrapper component bound to
|
|
49
51
|
* `preset`, plus the `layout` axis that preset sets by its own name.
|
|
50
|
-
* Matches `@markii/html`'s `layoutWrapperEntry`.
|
|
51
52
|
*/
|
|
52
53
|
function layoutWrapperEntry(preset) {
|
|
53
54
|
return {
|
|
@@ -57,14 +58,16 @@ function layoutWrapperEntry(preset) {
|
|
|
57
58
|
};
|
|
58
59
|
}
|
|
59
60
|
/**
|
|
60
|
-
* The built-in standard components, pre-registered under their names
|
|
61
|
-
*
|
|
62
|
-
* `defaultRegistry`
|
|
63
|
-
* `
|
|
64
|
-
* `AnsiRegistryEntry.selfLayout`) because they draw a single long
|
|
61
|
+
* The built-in standard components, pre-registered under their names: the
|
|
62
|
+
* same 23 names as `@markii/html`'s `defaultHtmlRegistry` and
|
|
63
|
+
* `@markii/react`'s `defaultRegistry`. `card`, `callout`, `divider`, and
|
|
64
|
+
* `table` are marked `selfLayout` because they draw a single long
|
|
65
65
|
* frame/bar/rule/grid a generic post-render `applyLayout` narrow/pad would
|
|
66
|
-
* corrupt or hard-break mid-glyph
|
|
67
|
-
*
|
|
66
|
+
* corrupt or hard-break mid-glyph. `row`, `tabs`, and `details` are the
|
|
67
|
+
* three directives `render.tsx` gives real Ink layout/state to directly
|
|
68
|
+
* (see `registry.ts`'s and `render.tsx`'s doc comments); their REGISTERED
|
|
69
|
+
* `component` here is the STRING-mode fallback, reached only when one of
|
|
70
|
+
* them is nested inside a self-drawing container's own body.
|
|
68
71
|
*/
|
|
69
72
|
export const defaultAnsiRegistry = createAnsiRegistry({
|
|
70
73
|
callout: {
|
package/dist/components/kbd.d.ts
CHANGED
package/dist/components/kbd.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { childrenText } from '../registry.js';
|
|
1
2
|
/**
|
|
2
3
|
* `:kbd[Ctrl+S]` — a styled keycap for an inline text directive. Terminal
|
|
3
4
|
* form: bracketed bold text, e.g. `[Ctrl+S]`. Takes no attributes; its inner
|
|
4
5
|
* content (already rendered plain text) is the key label.
|
|
5
6
|
*/
|
|
6
|
-
export const Kbd = (
|
|
7
|
-
return `[${ctx.bold(children
|
|
7
|
+
export const Kbd = ({ children, ctx }) => {
|
|
8
|
+
return `[${ctx.bold(childrenText(children))}]`;
|
|
8
9
|
};
|
|
@@ -1,34 +1,19 @@
|
|
|
1
1
|
import type { LayoutAxis } from '@markii/stdlib';
|
|
2
|
-
import type
|
|
2
|
+
import { type AnsiComponent } from '../registry.js';
|
|
3
3
|
/**
|
|
4
4
|
* 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.
|
|
5
|
+
* aliases of the one shared implementation below (`createLayoutWrapper`).
|
|
6
|
+
* There is deliberately no `normal` alias: the default needs no wrapper at
|
|
7
|
+
* all. Every name here is ALSO one of `@markii/stdlib`'s own width/align
|
|
8
|
+
* preset values.
|
|
12
9
|
*/
|
|
13
10
|
export declare const LAYOUT_WRAPPER_PRESETS: readonly ["center", "left", "right", "wide", "narrow", "full", "fit"];
|
|
14
11
|
export type LayoutWrapperPreset = (typeof LAYOUT_WRAPPER_PRESETS)[number];
|
|
15
12
|
/**
|
|
16
13
|
* Creates the registry component for one of docs/format.md's layout-wrapper
|
|
17
14
|
* container names. One shared implementation, bound to `preset` at
|
|
18
|
-
* registration time
|
|
19
|
-
*
|
|
20
|
-
* both reserved keys before this ever runs.
|
|
21
|
-
*
|
|
22
|
-
* A wrapper sets ONE axis by its own NAME (docs/spec.md §3): `preset` itself
|
|
23
|
-
* IS that axis's value (`center` sets `align: 'center'`; `narrow` sets
|
|
24
|
-
* `width: 'narrow'`), so it is applied unconditionally, whatever the author
|
|
25
|
-
* wrote for that axis's own reserved attribute (already discarded — the
|
|
26
|
-
* name always wins). The OTHER axis, when the author supplied it, arrives
|
|
27
|
-
* as `ctx.layout` (`render.ts` resolved it on this wrapper's behalf, since
|
|
28
|
-
* this wrapper is the directive's registered `layout` scope). Both are
|
|
29
|
-
* merged into one `ResolvedLayoutPresets` and applied together via
|
|
30
|
-
* `../layout.js`'s `applyLayout`, so `:::center{width=fit}` narrows AND
|
|
31
|
-
* centers in one pass.
|
|
15
|
+
* registration time: it never reads `attributes` at all — `render.tsx`
|
|
16
|
+
* already stripped both reserved keys before this ever runs.
|
|
32
17
|
*/
|
|
33
18
|
export declare function createLayoutWrapper(preset: LayoutWrapperPreset): AnsiComponent;
|
|
34
19
|
/** The layout axis `preset` sets by its own name. Mirrors `@markii/html`'s `layoutWrapperPresetAxis`. */
|