@markii/ansi 0.15.0 → 0.16.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/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 +24 -13
- package/dist/components/callout.js +45 -21
- package/dist/components/card.d.ts +24 -2
- package/dist/components/card.js +41 -12
- 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 +9 -16
- package/dist/components/details-string.d.ts +26 -0
- package/dist/components/details-string.js +39 -0
- package/dist/components/details.d.ts +30 -10
- package/dist/components/details.js +27 -25
- package/dist/components/divider.js +9 -11
- 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 +6 -14
- package/dist/components/rating.d.ts +1 -2
- package/dist/components/rating.js +2 -3
- package/dist/components/row.d.ts +33 -20
- package/dist/components/row.js +33 -51
- package/dist/components/stat.d.ts +3 -8
- package/dist/components/stat.js +4 -9
- package/dist/components/tab.d.ts +9 -8
- package/dist/components/tab.js +12 -10
- package/dist/components/table-grid.js +1 -2
- package/dist/components/table.d.ts +5 -10
- package/dist/components/table.js +7 -12
- package/dist/components/tabs-string.d.ts +17 -0
- package/dist/components/tabs-string.js +30 -0
- package/dist/components/tabs.d.ts +29 -14
- package/dist/components/tabs.js +37 -30
- package/dist/index.d.ts +2 -4
- package/dist/index.js +6 -7
- package/dist/ink-string.d.ts +12 -0
- package/dist/ink-string.js +41 -0
- package/dist/interactive.d.ts +41 -0
- package/dist/interactive.js +64 -0
- package/dist/layout.js +1 -2
- package/dist/registry.d.ts +91 -100
- package/dist/registry.js +17 -9
- package/dist/render.d.ts +39 -24
- package/dist/render.js +676 -373
- package/dist/text-grid.d.ts +54 -0
- package/dist/text-grid.js +249 -0
- package/package.json +7 -4
- 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,12 +1,32 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ColorLevel } from '../ansi.js';
|
|
2
3
|
/**
|
|
3
|
-
* `:::details{title="..." open} ... :::` — a collapsible disclosure
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* `:::details{title="..." open} ... :::` — a collapsible disclosure, built
|
|
5
|
+
* as a real Ink element (`render.tsx`'s `renderDetailsElement` calls this
|
|
6
|
+
* directly, the same way it calls `InteractiveTabs` for `tabs` — see
|
|
7
|
+
* `registry.ts`'s and `render.tsx`'s doc comments).
|
|
8
|
+
*
|
|
9
|
+
* Non-interactive (the default, and the render-once path): ALWAYS shows the
|
|
10
|
+
* body, exactly like the pre-Ink engine — a static render has no
|
|
11
|
+
* expand/collapse affordance. The bare `open` attribute only changes the
|
|
12
|
+
* marker glyph (`▾` for `open`, `▸` for folded-by-default), a quiet hint at
|
|
13
|
+
* the note's own authored default.
|
|
14
|
+
*
|
|
15
|
+
* Interactive: starts CLOSED unless `open` was given; Enter toggles it when
|
|
16
|
+
* this `details` instance is focused (`useIsFocused`). When focused, the
|
|
17
|
+
* summary line also carries a dim hint (`enter to open`/`enter to close`,
|
|
18
|
+
* matching the current state) and the whole line is drawn in the accent
|
|
19
|
+
* color with a leading `› ` glyph (`useFocusMarker`), so focus is visible
|
|
20
|
+
* even on a theme with no perceptible accent color; unfocused, the line
|
|
21
|
+
* gets two leading spaces instead, so nothing shifts horizontally when
|
|
22
|
+
* focus moves onto or off of this block.
|
|
11
23
|
*/
|
|
12
|
-
export
|
|
24
|
+
export interface InteractiveDetailsProps {
|
|
25
|
+
title: string;
|
|
26
|
+
body: ReactNode;
|
|
27
|
+
defaultOpen: boolean;
|
|
28
|
+
color: ColorLevel;
|
|
29
|
+
interactive: boolean;
|
|
30
|
+
focusId: number | undefined;
|
|
31
|
+
}
|
|
32
|
+
export declare function InteractiveDetails({ title, body, defaultOpen, color, interactive, focusId, }: InteractiveDetailsProps): ReactNode;
|
|
@@ -1,25 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
if (!
|
|
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 { useFocusMarker, useIsFocused } from '../interactive.js';
|
|
6
|
+
export function InteractiveDetails({ title, body, defaultOpen, color, interactive, focusId, }) {
|
|
7
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
8
|
+
const focused = useIsFocused(focusId);
|
|
9
|
+
useInput((_input, key) => {
|
|
10
|
+
if (key.return)
|
|
11
|
+
setOpen((current) => !current);
|
|
12
|
+
}, { isActive: interactive && focused });
|
|
13
|
+
// Every hook this component calls runs BEFORE the non-interactive early
|
|
14
|
+
// return below, so the hook order is identical in both modes. The summary
|
|
15
|
+
// line is cheap to build and unused in the non-interactive branch; that
|
|
16
|
+
// waste is the price of never making a hook call conditional.
|
|
17
|
+
const openGlyph = open ? '▾' : '▸';
|
|
18
|
+
const hint = focused ? (open ? ' enter to close' : ' enter to open') : '';
|
|
19
|
+
const summaryLine = dim(`${openGlyph} ${bold(title, color)}${hint}`, color);
|
|
20
|
+
const focusGlyph = focused ? '› ' : ' ';
|
|
21
|
+
const headingLine = useFocusMarker(`${focusGlyph}${summaryLine}`, focusId);
|
|
22
|
+
if (!interactive) {
|
|
23
|
+
const glyph = defaultOpen ? '▾' : '▸';
|
|
24
|
+
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 })] }));
|
|
25
|
+
}
|
|
26
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: headingLine }), open && (_jsx(Box, { marginLeft: 2, flexDirection: "column", children: body }))] }));
|
|
27
|
+
}
|
|
@@ -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',
|
|
@@ -20,7 +20,7 @@ const SHORT_SIDE_RULE = 2;
|
|
|
20
20
|
/** Builds a full-`width` rule with `label` woven in at `align`, e.g. `── Part 2 ─────────`. */
|
|
21
21
|
function labeledRule(ruleChar, label, width, align) {
|
|
22
22
|
const labelText = ` ${label} `;
|
|
23
|
-
const remaining = Math.max(0, width -
|
|
23
|
+
const remaining = Math.max(0, width - measureWidth(labelText));
|
|
24
24
|
let left;
|
|
25
25
|
if (align === 'left')
|
|
26
26
|
left = Math.min(SHORT_SIDE_RULE, remaining);
|
|
@@ -42,15 +42,13 @@ function labeledRule(ruleChar, label, width, align) {
|
|
|
42
42
|
* within the width. Dimmed, matching the plain thematic-break rendering.
|
|
43
43
|
*
|
|
44
44
|
* Registered `selfLayout` (`registry.ts`'s `AnsiRegistryEntry.selfLayout`):
|
|
45
|
-
* a full-width rule is ONE long run with no spaces in it, so
|
|
45
|
+
* a full-width rule is ONE long run with no spaces in it, so the generic
|
|
46
46
|
* 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`.
|
|
47
|
+
* single over-long "word" and hard-break it — this component instead reads
|
|
48
|
+
* `ctx.layout` and draws its own rule at the resolved width from the start.
|
|
51
49
|
*/
|
|
52
50
|
const FIT_DEFAULT_WIDTH = 10;
|
|
53
|
-
export const Divider = (attributes,
|
|
51
|
+
export const Divider = ({ attributes, ctx }) => {
|
|
54
52
|
const rawVariant = attributes.variant ?? 'line';
|
|
55
53
|
const variant = isDividerVariant(rawVariant)
|
|
56
54
|
? rawVariant
|
|
@@ -61,20 +59,20 @@ export const Divider = (attributes, _children, ctx) => {
|
|
|
61
59
|
const labelAlign = rawLabelAlign && isDividerLabelAlign(rawLabelAlign)
|
|
62
60
|
? rawLabelAlign
|
|
63
61
|
: 'center';
|
|
64
|
-
const naturalWidth = label ?
|
|
62
|
+
const naturalWidth = label ? measureWidth(label) + 4 : FIT_DEFAULT_WIDTH;
|
|
65
63
|
const width = selfLayoutWidth(ctx.layout, ctx.width, naturalWidth);
|
|
66
64
|
let line;
|
|
67
65
|
if (variant === 'ornament') {
|
|
68
66
|
const inner = label
|
|
69
67
|
? `${ORNAMENT_GLYPH} ${label} ${ORNAMENT_GLYPH}`
|
|
70
68
|
: ORNAMENT_GLYPH;
|
|
71
|
-
line =
|
|
69
|
+
line = padText(inner, width, labelAlign);
|
|
72
70
|
}
|
|
73
71
|
else {
|
|
74
72
|
const ruleChar = RULE_CHAR[variant];
|
|
75
73
|
line = label
|
|
76
74
|
? labeledRule(ruleChar, label, width, labelAlign)
|
|
77
|
-
:
|
|
75
|
+
: ruleLine(width, ruleChar);
|
|
78
76
|
}
|
|
79
77
|
return selfLayoutAlign(ctx.dim(line), ctx.layout, ctx.width);
|
|
80
78
|
};
|
|
@@ -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`. */
|
|
@@ -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;
|
|
@@ -38,19 +38,11 @@ const EMPTY_CHAR = '░';
|
|
|
38
38
|
const MAX_BAR_WIDTH = 30;
|
|
39
39
|
const MIN_BAR_WIDTH = 3;
|
|
40
40
|
/**
|
|
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.
|
|
41
|
+
* `::progress{value=3 max=5 label="tasks"}` — a meter bar. Data binding
|
|
42
|
+
* (§8): a bound number supplies `value`; a bound object may supply
|
|
43
|
+
* `value`/`max` — explicit attributes always win.
|
|
52
44
|
*/
|
|
53
|
-
export const Progress = (attributes,
|
|
45
|
+
export const Progress = ({ attributes, ctx }) => {
|
|
54
46
|
const { data, dataStatus, dataFailureKind } = ctx;
|
|
55
47
|
const bound = safeRead(() => dataStatus === 'missing' || dataStatus === 'error'
|
|
56
48
|
? {}
|
|
@@ -70,7 +62,7 @@ export const Progress = (attributes, _children, ctx) => {
|
|
|
70
62
|
const suffix = dataStateSuffix(dataStatus, dataFailureKind);
|
|
71
63
|
const token = failureToken(dataFailureKind);
|
|
72
64
|
const percentPart = ` ${ctx.text(percentText)}${suffix ? (token ? ctx.style(suffix, token) : ctx.dim(suffix)) : ''}`;
|
|
73
|
-
const overhead =
|
|
65
|
+
const overhead = measureWidth(labelPart) + measureWidth(percentText) + 1;
|
|
74
66
|
const barWidth = Math.max(MIN_BAR_WIDTH, Math.min(MAX_BAR_WIDTH, ctx.width - overhead));
|
|
75
67
|
const filledCount = Math.round((percent / 100) * barWidth);
|
|
76
68
|
const bar = FILLED_CHAR.repeat(filledCount) + EMPTY_CHAR.repeat(barWidth - filledCount);
|
|
@@ -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,10 +14,9 @@ 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 }) => {
|
|
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 = '';
|