@markii/ansi 0.0.1 → 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
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { AnsiTheme } from './theme.js';
|
|
3
|
+
import type { ColorLevel } from './ansi.js';
|
|
4
|
+
/** Whether the component identified by `focusId` is the currently focused one. `undefined` (a non-interactive render, or a component that never got a `focusId`) is never focused. */
|
|
5
|
+
export declare function useIsFocused(focusId: number | undefined): boolean;
|
|
6
|
+
/** Wraps `text` in the accent-colored focus marker when interactive AND `focusId` is the focused one; otherwise returns `text` unchanged. */
|
|
7
|
+
export declare function useFocusMarker(text: string, focusId: number | undefined): string;
|
|
8
|
+
/**
|
|
9
|
+
* One focusable component, in the same document order `render.tsx`'s
|
|
10
|
+
* counting walk assigns `focusId`s in — `focusables[focusId]` is always
|
|
11
|
+
* that component's descriptor. `label` is a stable identity: for `tabs`,
|
|
12
|
+
* the FIRST panel's label, never the currently active one. A `tabs` block's
|
|
13
|
+
* active panel is its own component state, so naming it on the status line
|
|
14
|
+
* would need a report channel back up to this root on every switch; the
|
|
15
|
+
* first label identifies the block just as well and cannot loop.
|
|
16
|
+
*/
|
|
17
|
+
export interface FocusableDescriptor {
|
|
18
|
+
kind: 'tabs' | 'details';
|
|
19
|
+
label: string;
|
|
20
|
+
}
|
|
21
|
+
export interface InteractiveRootProps {
|
|
22
|
+
/** Every focusable component the walk assigned a `focusId` to, in `focusId` order (empty for none). */
|
|
23
|
+
focusables: readonly FocusableDescriptor[];
|
|
24
|
+
/** Called when the reader presses `q`. The engine owns no process state: it never calls `process.exit` itself (AGENTS.md, batch-10 brief). */
|
|
25
|
+
onExit?: () => void;
|
|
26
|
+
color: ColorLevel;
|
|
27
|
+
theme: AnsiTheme;
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The root of an interactive render: owns which `focusId` is focused, moves
|
|
32
|
+
* it on up/down/j/k, calls `onExit` on `q`, and renders the one-line status
|
|
33
|
+
* bar (batch 10.1) naming the focused block and the key legend. Rendered
|
|
34
|
+
* only when the caller actually wants a live, keyboard-driven view:
|
|
35
|
+
* `buildMarkElement` mounts this when `interactive` is true, but the
|
|
36
|
+
* render-ONCE string path (`ink-string.ts`, `renderRootToString`) never
|
|
37
|
+
* reaches it at all — `tabs`/`details` still fall back to `FocusContext`'s
|
|
38
|
+
* default (uninteractive) value there, so a string render never sees a
|
|
39
|
+
* focus marker, a glyph, or a status line.
|
|
40
|
+
*/
|
|
41
|
+
export declare function InteractiveRoot({ focusables, onExit, color, theme, children, }: InteractiveRootProps): ReactNode;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useMemo, useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { dim } from './ansi.js';
|
|
5
|
+
import { style } from './style.js';
|
|
6
|
+
const FocusContext = createContext({
|
|
7
|
+
interactive: false,
|
|
8
|
+
focusedId: undefined,
|
|
9
|
+
focusMarker: (text) => text,
|
|
10
|
+
});
|
|
11
|
+
/** Whether the component identified by `focusId` is the currently focused one. `undefined` (a non-interactive render, or a component that never got a `focusId`) is never focused. */
|
|
12
|
+
export function useIsFocused(focusId) {
|
|
13
|
+
const api = useContext(FocusContext);
|
|
14
|
+
return focusId !== undefined && api.interactive && api.focusedId === focusId;
|
|
15
|
+
}
|
|
16
|
+
/** Wraps `text` in the accent-colored focus marker when interactive AND `focusId` is the focused one; otherwise returns `text` unchanged. */
|
|
17
|
+
export function useFocusMarker(text, focusId) {
|
|
18
|
+
const api = useContext(FocusContext);
|
|
19
|
+
const focused = focusId !== undefined && api.interactive && api.focusedId === focusId;
|
|
20
|
+
return focused ? api.focusMarker(text) : text;
|
|
21
|
+
}
|
|
22
|
+
/** The key legend shown on the status line, identical regardless of what is focused. */
|
|
23
|
+
const KEY_LEGEND = '↑↓/jk focus ←→/tab switch enter open/close q quit';
|
|
24
|
+
/**
|
|
25
|
+
* The root of an interactive render: owns which `focusId` is focused, moves
|
|
26
|
+
* it on up/down/j/k, calls `onExit` on `q`, and renders the one-line status
|
|
27
|
+
* bar (batch 10.1) naming the focused block and the key legend. Rendered
|
|
28
|
+
* only when the caller actually wants a live, keyboard-driven view:
|
|
29
|
+
* `buildMarkElement` mounts this when `interactive` is true, but the
|
|
30
|
+
* render-ONCE string path (`ink-string.ts`, `renderRootToString`) never
|
|
31
|
+
* reaches it at all — `tabs`/`details` still fall back to `FocusContext`'s
|
|
32
|
+
* default (uninteractive) value there, so a string render never sees a
|
|
33
|
+
* focus marker, a glyph, or a status line.
|
|
34
|
+
*/
|
|
35
|
+
export function InteractiveRoot({ focusables, onExit, color, theme, children, }) {
|
|
36
|
+
const focusableCount = focusables.length;
|
|
37
|
+
const [focusIndex, setFocusIndex] = useState(0);
|
|
38
|
+
useInput((input, key) => {
|
|
39
|
+
if (input === 'q') {
|
|
40
|
+
onExit?.();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (focusableCount === 0)
|
|
44
|
+
return;
|
|
45
|
+
if (key.downArrow || input === 'j') {
|
|
46
|
+
setFocusIndex((current) => (current + 1) % focusableCount);
|
|
47
|
+
}
|
|
48
|
+
else if (key.upArrow || input === 'k') {
|
|
49
|
+
setFocusIndex((current) => (current - 1 + focusableCount) % focusableCount);
|
|
50
|
+
}
|
|
51
|
+
}, { isActive: true });
|
|
52
|
+
const focusedId = focusableCount > 0 ? focusIndex % focusableCount : undefined;
|
|
53
|
+
const api = useMemo(() => ({
|
|
54
|
+
interactive: true,
|
|
55
|
+
focusedId,
|
|
56
|
+
focusMarker: (text) => style(text, '--mk-accent', theme, color),
|
|
57
|
+
}), [focusedId, color, theme]);
|
|
58
|
+
const focused = focusedId !== undefined ? focusables[focusedId] : undefined;
|
|
59
|
+
const statusLabel = focused
|
|
60
|
+
? `${focused.kind} "${focused.label}"`
|
|
61
|
+
: 'nothing focusable';
|
|
62
|
+
const statusLine = dim(`${statusLabel} ${KEY_LEGEND}`, color);
|
|
63
|
+
return (_jsx(FocusContext.Provider, { value: api, children: _jsxs(Box, { flexDirection: "column", children: [children, _jsx(Text, { children: statusLine })] }) }));
|
|
64
|
+
}
|
package/dist/layout.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ALIGN_PRESETS, LAYOUT_ATTRIBUTE_KEYS, WIDTH_PRESETS, } from '@markii/stdlib';
|
|
2
|
-
import { pad, wrap } from './
|
|
3
|
-
import { measure } from './measure.js';
|
|
2
|
+
import { padText as pad, wrapText as wrap, measureWidth as measure, } from './text-grid.js';
|
|
4
3
|
/**
|
|
5
4
|
* The terminal counterpart of `@markii/html`'s `layout.ts`: the same closed
|
|
6
5
|
* `width`/`align` vocabulary from `@markii/stdlib`, but resolved to COLUMN
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* The registry contract for the terminal engine: the string-emitting twin of
|
|
3
|
-
* `@markii/html`'s `registry.ts`, itself the string-emitting twin of
|
|
4
|
-
* `@markii/react`'s `registry.ts`. A component here is a plain function from
|
|
5
|
-
* attributes and already-rendered children TEXT to an output string — no
|
|
6
|
-
* hast, no React, no HTML. The alias, merge, and hostile-configuration rules
|
|
7
|
-
* are kept identical to both existing renderers, so a note resolves the same
|
|
8
|
-
* way in all three.
|
|
9
|
-
*/
|
|
1
|
+
import type { FC, ReactNode } from 'react';
|
|
10
2
|
import type { FailureKind, ValueStatus } from './value-types.js';
|
|
11
3
|
import type { LayoutAxis, OnDiagnostic } from '@markii/stdlib';
|
|
12
4
|
import type { ColorLevel } from './ansi.js';
|
|
@@ -14,6 +6,23 @@ import type { AnsiTheme, Tier1Token } from './theme.js';
|
|
|
14
6
|
import type { ResolvedLayoutAttributes } from './layout.js';
|
|
15
7
|
import type { ResolveImageSrc } from './image-resolve.js';
|
|
16
8
|
import type { ResolveHref } from './href-resolve.js';
|
|
9
|
+
/**
|
|
10
|
+
* The registry contract for the terminal engine, batch-10 (the Ink
|
|
11
|
+
* rewrite): a component is a React function component receiving
|
|
12
|
+
* `{ attributes, children, ctx }`, where `children` is ALREADY a built Ink
|
|
13
|
+
* element tree (a `ReactNode`) rather than the lazy string-producing
|
|
14
|
+
* `AnsiChildren` handle the pre-Ink engine used. Building children eagerly
|
|
15
|
+
* is the batch-10 brief's own instruction (`AGENTS.md`'s architecture
|
|
16
|
+
* section is silent on this; the brief is explicit: "Delete... the lazy-
|
|
17
|
+
* children machinery"): Ink/Yoga lays a tree out AFTER it is built, exactly
|
|
18
|
+
* like a browser reflows HTML, so a component no longer needs to ask for a
|
|
19
|
+
* narrower re-render of its own body the way the old string engine did —
|
|
20
|
+
* the one standard component that still needs a per-cell WIDTH NUMBER before
|
|
21
|
+
* it draws (`row`, whose cells may hold a self-drawing box like `card`) gets
|
|
22
|
+
* that from `render.tsx`'s own walk, which still threads `width` top-down
|
|
23
|
+
* exactly as before; see `render.tsx`'s `renderRow` for why that one case is
|
|
24
|
+
* handled at the walk level rather than through a registry-facing API.
|
|
25
|
+
*/
|
|
17
26
|
/**
|
|
18
27
|
* Attributes parsed off a directive, e.g. `{type=warning title="Careful"}`. A
|
|
19
28
|
* bare attribute (present but valueless, e.g. `{collapsed}`) arrives as
|
|
@@ -32,28 +41,47 @@ export interface ValueResolution {
|
|
|
32
41
|
failureKind?: FailureKind;
|
|
33
42
|
}
|
|
34
43
|
/**
|
|
35
|
-
* The render context handed to every component. Mirrors
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
44
|
+
* The render context handed to every component. Mirrors the pre-Ink
|
|
45
|
+
* engine's `AnsiRenderContext` for every field EXCEPT the string-layout
|
|
46
|
+
* helpers (`wrap`/`pad`/`columns`/`frame`/`rule`), which are dropped:
|
|
47
|
+
* ordinary prose now wraps through Ink's own layout, and the handful of
|
|
48
|
+
* self-drawing standard components (`card`, `callout`, `divider`, `chart`,
|
|
49
|
+
* `table`) that still need exact box-drawing glyphs use `./text-grid.js`'s
|
|
50
|
+
* private helpers directly rather than through the context.
|
|
42
51
|
*/
|
|
43
52
|
export interface AnsiRenderContext {
|
|
44
|
-
/** The columns available to this directive's own output. A component
|
|
53
|
+
/** The columns available to this directive's own output. A self-drawing component sizes its own frame to this width (or to `layout`'s resolved preset of it). */
|
|
45
54
|
width: number;
|
|
46
55
|
/**
|
|
47
56
|
* The prefix an enclosing block (a blockquote, a nested list) has already
|
|
48
|
-
* applied to every line above this directive
|
|
49
|
-
* component
|
|
50
|
-
*
|
|
57
|
+
* applied to every line above this directive, informational only: a
|
|
58
|
+
* component draws its own content at `width` and the walk applies the
|
|
59
|
+
* enclosing indent as an Ink `Box` wrapper, not as a string prefix.
|
|
51
60
|
*/
|
|
52
61
|
indent: string;
|
|
53
62
|
/** The resolved color depth for this render. */
|
|
54
63
|
color: ColorLevel;
|
|
55
64
|
/** The resolved theme for this render. */
|
|
56
65
|
theme: AnsiTheme;
|
|
66
|
+
/**
|
|
67
|
+
* Whether this render is in INTERACTIVE mode (a live viewer), as opposed
|
|
68
|
+
* to the default render-once mode. NAME COLLISION, documented at both
|
|
69
|
+
* declarations: this is UNRELATED to Ink's own `render()` option also
|
|
70
|
+
* spelled `interactive` (`ink-string.ts`'s doc comment) — that one
|
|
71
|
+
* controls which escape sequences Ink emits around a frame; this one
|
|
72
|
+
* controls which CONTENT a component renders (`tabs` shows only the
|
|
73
|
+
* active panel, `details` starts closed unless `open` is present) and
|
|
74
|
+
* whether it registers a keyboard handler at all.
|
|
75
|
+
*/
|
|
76
|
+
interactive: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* A stable, document-order identity for a FOCUSABLE component (currently
|
|
79
|
+
* `tabs` and `details`) when `interactive` is true; `undefined` when not
|
|
80
|
+
* interactive, or for any other component. Assigned once by `render.tsx`
|
|
81
|
+
* as it walks the tree (see `render.tsx`'s `WalkContext.nextFocusId`), so
|
|
82
|
+
* a component never has to compute its own position in document order.
|
|
83
|
+
*/
|
|
84
|
+
focusId?: number;
|
|
57
85
|
/** Applies `theme`'s color for `token` at this render's `color` level. Unchanged at `'none'` or for a `null` theme entry. */
|
|
58
86
|
style(text: string, token: Tier1Token): string;
|
|
59
87
|
/** SGR bold, pre-bound to this render's color level. */
|
|
@@ -66,26 +94,11 @@ export interface AnsiRenderContext {
|
|
|
66
94
|
underline(text: string): string;
|
|
67
95
|
/** SGR inverse/reverse video, pre-bound to this render's color level. */
|
|
68
96
|
inverse(text: string): string;
|
|
69
|
-
/** Greedy word wrap, pre-bound to nothing (width is explicit here since a component may wrap narrower than its own `ctx.width`, e.g. inside its own frame). */
|
|
70
|
-
wrap(text: string, width: number): string[];
|
|
71
|
-
/** Pads `text` to `width` columns, aligned `left`/`center`/`right`. */
|
|
72
|
-
pad(text: string, width: number, align: 'left' | 'center' | 'right'): string;
|
|
73
|
-
/** Places blocks side by side; see `./box.js`'s `columns`. */
|
|
74
|
-
columns(blocks: readonly string[], widths: readonly number[], gutter: number): string;
|
|
75
|
-
/** Draws a box around `block`; see `./box.js`'s `frame`. */
|
|
76
|
-
frame(block: string, options: {
|
|
77
|
-
style: 'solid' | 'dashed';
|
|
78
|
-
title?: string;
|
|
79
|
-
width: number;
|
|
80
|
-
}): string;
|
|
81
|
-
/** A full-width horizontal rule; see `./box.js`'s `rule`. */
|
|
82
|
-
rule(width: number, char?: string): string;
|
|
83
97
|
/**
|
|
84
98
|
* The sanitizer a component MUST run any author-supplied string through
|
|
85
|
-
* before printing it
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* `stripControlCharacters`).
|
|
99
|
+
* before printing it: strips every control character an untrusted note
|
|
100
|
+
* could use to move the cursor or smuggle an escape sequence
|
|
101
|
+
* (`./sanitize.js`'s `stripControlCharacters`).
|
|
89
102
|
*/
|
|
90
103
|
text(value: string): string;
|
|
91
104
|
/** Resolves a `data=`/`:value[...]` name against the current render's store/vault. Never throws. */
|
|
@@ -108,76 +121,39 @@ export interface AnsiRenderContext {
|
|
|
108
121
|
onDiagnostic?: OnDiagnostic;
|
|
109
122
|
}
|
|
110
123
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* One top-level child of a directive's own body, independently renderable
|
|
122
|
-
* at its own width — the piece `row` needs and no other standard component
|
|
123
|
-
* does: it must decide each cell's column width BEFORE that cell's content
|
|
124
|
-
* (including a self-drawing box like `card`) is rendered, not re-wrap an
|
|
125
|
-
* already-drawn frame afterward. `name` is the child's own resolved
|
|
126
|
-
* directive name (e.g. `'cell'`) when the child is itself a directive, and
|
|
127
|
-
* `undefined` for a plain block (a paragraph, a list) or a directive with a
|
|
128
|
-
* different name — enough for a container to recognize a semantically
|
|
129
|
-
* tagged child without ever seeing a hast node.
|
|
124
|
+
* One registry component: a React function component. `attributes` are the
|
|
125
|
+
* directive's raw string attributes (bare attributes as `null`); `children`
|
|
126
|
+
* is the directive's already-rendered inner markdown, an Ink element tree;
|
|
127
|
+
* `ctx` is `AnsiRenderContext` above. Attribute parsing, validation, and
|
|
128
|
+
* defaulting are the component's own job, exactly as in the other two
|
|
129
|
+
* engines' contracts. A component MAY use React hooks (`useState`,
|
|
130
|
+
* `useInput`, ...) since `render.tsx` always invokes it as a JSX element,
|
|
131
|
+
* never as a plain function call — `tabs` and `details` need this for
|
|
132
|
+
* interactive mode.
|
|
130
133
|
*/
|
|
131
|
-
export interface
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
export interface AnsiComponentProps {
|
|
135
|
+
attributes: DirectiveAttributes;
|
|
136
|
+
children: ReactNode;
|
|
137
|
+
ctx: AnsiRenderContext;
|
|
134
138
|
}
|
|
135
|
-
|
|
136
|
-
* The children a component receives. Calling it directly renders the
|
|
137
|
-
* directive's WHOLE body as one flattened string — what every component
|
|
138
|
-
* except `row` wants, and what used to be eagerly computed as a plain
|
|
139
|
-
* string before the component ever ran. `options` narrows the width/indent
|
|
140
|
-
* for that one call (a self-layout box's inner budget); omitted, it renders
|
|
141
|
-
* at the width/indent the directive itself was given.
|
|
142
|
-
*
|
|
143
|
-
* `.parts` exposes each of the directive's own top-level children
|
|
144
|
-
* separately, in document order, each independently renderable at its own
|
|
145
|
-
* width via its own `render(options)` — see `AnsiChildPart`.
|
|
146
|
-
*
|
|
147
|
-
* Laziness is the point: nothing under this directive is rendered until a
|
|
148
|
-
* component actually calls `children()` or one of `.parts`' `render()`
|
|
149
|
-
* functions, so a container can decide sizes top-down before anything below
|
|
150
|
-
* it draws a single character.
|
|
151
|
-
*/
|
|
152
|
-
export type AnsiChildren = ((options?: AnsiChildrenOptions) => string) & {
|
|
153
|
-
readonly parts: readonly AnsiChildPart[];
|
|
154
|
-
};
|
|
155
|
-
/**
|
|
156
|
-
* One registry component: receives the directive's raw string attributes
|
|
157
|
-
* (bare attributes as `null`), a lazy handle onto its inner markdown
|
|
158
|
-
* (`AnsiChildren`, see above), and the render context, and returns the text
|
|
159
|
-
* to emit. Attribute parsing, validation, and defaulting are the
|
|
160
|
-
* component's own job, exactly as in the other two engines' contracts.
|
|
161
|
-
*/
|
|
162
|
-
export type AnsiComponent = (attributes: DirectiveAttributes, children: AnsiChildren, ctx: AnsiRenderContext) => string;
|
|
139
|
+
export type AnsiComponent = FC<AnsiComponentProps>;
|
|
163
140
|
/** One registry entry: the component plus whether it is meant to be used inline vs as a block, and whether it is a layout scope. Mirrors `@markii/html`'s `HtmlRegistryEntry`. */
|
|
164
141
|
export interface AnsiRegistryEntry {
|
|
165
142
|
component: AnsiComponent;
|
|
166
143
|
inline?: boolean;
|
|
167
144
|
layout?: LayoutAxis;
|
|
168
145
|
/**
|
|
169
|
-
* Marks a component that draws its OWN
|
|
170
|
-
* than plain wrapped
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* `
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
* that a generic wrap/pad would break."
|
|
146
|
+
* Marks a component that draws its OWN fixed-width block (box-drawing
|
|
147
|
+
* glyphs, a rule, a sparkline) rather than plain wrapped prose — `card`,
|
|
148
|
+
* `callout`, `divider`, `table`, `chart` in the standard set. A generic
|
|
149
|
+
* post-render narrow/align pass would corrupt a pre-drawn frame's
|
|
150
|
+
* box-drawing characters, so a `selfLayout` component instead receives the
|
|
151
|
+
* resolved `width`/`align` presets as `ctx.layout` and is trusted to size
|
|
152
|
+
* its own frame correctly; `render.tsx` skips its usual post-render
|
|
153
|
+
* `applyLayout` wrapping for it. Kept from the pre-Ink engine: Ink's own
|
|
154
|
+
* layout does not solve this, because the content in question is a
|
|
155
|
+
* pre-built STRING (see `text-grid.ts`'s module doc comment), not
|
|
156
|
+
* Ink primitives Yoga can reflow.
|
|
181
157
|
*/
|
|
182
158
|
selfLayout?: boolean;
|
|
183
159
|
}
|
|
@@ -225,3 +201,18 @@ export interface ResolvedDirective {
|
|
|
225
201
|
* throws.
|
|
226
202
|
*/
|
|
227
203
|
export declare function resolveDirectiveAlias(registry: AnsiRegistry, name: string, attributes: DirectiveAttributes): ResolvedDirective;
|
|
204
|
+
/**
|
|
205
|
+
* Reads a component's `children` prop back as plain text. `render.tsx`
|
|
206
|
+
* always hands most standard components (every one except `row`, `tabs`,
|
|
207
|
+
* `details`) an already-flattened STRING as `children` — see `render.tsx`'s
|
|
208
|
+
* top comment on why those three are the only ones that get a real Ink
|
|
209
|
+
* element tree instead. `children` is still typed `ReactNode` on
|
|
210
|
+
* `AnsiComponentProps` (the honest public contract: "an Ink element tree",
|
|
211
|
+
* per the batch-10 brief, and a string IS a valid `ReactNode`), so a
|
|
212
|
+
* component that needs its body AS TEXT (to measure it, frame it, indent
|
|
213
|
+
* it, or check whether it is empty) reads it through this helper rather
|
|
214
|
+
* than assuming the type; a non-string `children` degrades to `''` rather
|
|
215
|
+
* than throwing, matching this package's never-throw posture everywhere
|
|
216
|
+
* else.
|
|
217
|
+
*/
|
|
218
|
+
export declare function childrenText(children: ReactNode): string;
|
package/dist/registry.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The registry contract for the terminal engine: the string-emitting twin of
|
|
3
|
-
* `@markii/html`'s `registry.ts`, itself the string-emitting twin of
|
|
4
|
-
* `@markii/react`'s `registry.ts`. A component here is a plain function from
|
|
5
|
-
* attributes and already-rendered children TEXT to an output string — no
|
|
6
|
-
* hast, no React, no HTML. The alias, merge, and hostile-configuration rules
|
|
7
|
-
* are kept identical to both existing renderers, so a note resolves the same
|
|
8
|
-
* way in all three.
|
|
9
|
-
*/
|
|
10
1
|
/** The symbol an alias table hangs off a registry under. Mirrors `@markii/html`'s `REGISTRY_ALIASES`. */
|
|
11
2
|
export const REGISTRY_ALIASES = Symbol('markii.ansi.registry.aliases');
|
|
12
3
|
/** Reads a registry's alias table, or `undefined` if it has none. Returned as-is; treat as read-only. */
|
|
@@ -119,3 +110,20 @@ export function resolveDirectiveAlias(registry, name, attributes) {
|
|
|
119
110
|
attributes: mergeAliasAttributes(alias.attributes, attributes),
|
|
120
111
|
};
|
|
121
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Reads a component's `children` prop back as plain text. `render.tsx`
|
|
115
|
+
* always hands most standard components (every one except `row`, `tabs`,
|
|
116
|
+
* `details`) an already-flattened STRING as `children` — see `render.tsx`'s
|
|
117
|
+
* top comment on why those three are the only ones that get a real Ink
|
|
118
|
+
* element tree instead. `children` is still typed `ReactNode` on
|
|
119
|
+
* `AnsiComponentProps` (the honest public contract: "an Ink element tree",
|
|
120
|
+
* per the batch-10 brief, and a string IS a valid `ReactNode`), so a
|
|
121
|
+
* component that needs its body AS TEXT (to measure it, frame it, indent
|
|
122
|
+
* it, or check whether it is empty) reads it through this helper rather
|
|
123
|
+
* than assuming the type; a non-string `children` degrades to `''` rather
|
|
124
|
+
* than throwing, matching this package's never-throw posture everywhere
|
|
125
|
+
* else.
|
|
126
|
+
*/
|
|
127
|
+
export function childrenText(children) {
|
|
128
|
+
return typeof children === 'string' ? children : '';
|
|
129
|
+
}
|
package/dist/render.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
1
2
|
import { parse } from '@markii/core';
|
|
2
3
|
import type { MarkNode } from '@markii/core';
|
|
3
4
|
import { type ColorOption } from './ansi.js';
|
|
@@ -8,11 +9,14 @@ import { type ResolveImageSrc } from './image-resolve.js';
|
|
|
8
9
|
import { type ResolveHref } from './href-resolve.js';
|
|
9
10
|
import type { AnsiValueStore, AnsiVaultStore } from './value-types.js';
|
|
10
11
|
type MarkRoot = ReturnType<typeof parse>;
|
|
12
|
+
export interface ElementTabPanel {
|
|
13
|
+
label: string;
|
|
14
|
+
body: ReactNode;
|
|
15
|
+
}
|
|
11
16
|
/**
|
|
12
17
|
* Options for `renderMarkToAnsi`/`renderMarkNodeToAnsi`/`renderMarkInlineToAnsi`.
|
|
13
18
|
* `store`/`vault` are offered both here and as positional parameters; when
|
|
14
|
-
* both are given, the option wins
|
|
15
|
-
* `RenderMarkOptions`).
|
|
19
|
+
* both are given, the option wins.
|
|
16
20
|
*/
|
|
17
21
|
export interface RenderMarkOptions {
|
|
18
22
|
readonly width?: number;
|
|
@@ -23,21 +27,27 @@ export interface RenderMarkOptions {
|
|
|
23
27
|
readonly onDiagnostic?: OnDiagnostic;
|
|
24
28
|
readonly store?: AnsiValueStore;
|
|
25
29
|
readonly vault?: AnsiVaultStore;
|
|
30
|
+
/**
|
|
31
|
+
* Renders the document's CONTENT in interactive shape (tabs show only
|
|
32
|
+
* their active panel, details start closed unless `open`) without
|
|
33
|
+
* actually mounting a live keyboard loop — this render-once path always
|
|
34
|
+
* captures exactly one frame (`ink-string.ts`'s own `interactive: false`
|
|
35
|
+
* Ink render option is a SEPARATE thing; see its doc comment for the
|
|
36
|
+
* name collision). Defaults to `false`: unset, `tabs`/`details` render
|
|
37
|
+
* every panel/always-open exactly as the pre-Ink engine did.
|
|
38
|
+
*/
|
|
39
|
+
readonly interactive?: boolean;
|
|
26
40
|
}
|
|
27
41
|
/**
|
|
28
42
|
* Renders Markii text to a plain string (optionally carrying ANSI SGR/OSC 8
|
|
29
43
|
* escapes) using `registry` to resolve directive names. `registry` is
|
|
30
44
|
* OPTIONAL here, unlike the other two engines' required parameter, and
|
|
31
|
-
* defaults to `defaultAnsiRegistry
|
|
32
|
-
* therefore renders the unknown-component fallback).
|
|
45
|
+
* defaults to `defaultAnsiRegistry`.
|
|
33
46
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* fallback
|
|
38
|
-
* paragraph, heading, and list item to `options.width` (default 80). Pure
|
|
39
|
-
* and never-throwing: parsing is tolerant, unknown names always render a
|
|
40
|
-
* fallback, and any unexpected internal error degrades to the "failed to
|
|
47
|
+
* `async` since the first call in a process pays Ink/Yoga's one-time module-
|
|
48
|
+
* evaluation cost (`ink-string.ts`'s doc comment; `SPIKE-10-findings.md`'s
|
|
49
|
+
* "Item 3"). Never throws: parsing is tolerant, unknown names always render
|
|
50
|
+
* a fallback, and any unexpected internal error degrades to the "failed to
|
|
41
51
|
* render document" line.
|
|
42
52
|
*
|
|
43
53
|
* `options.color` resolves via `./ansi.js`'s `resolveColorOption`: `'auto'`
|
|
@@ -45,25 +55,30 @@ export interface RenderMarkOptions {
|
|
|
45
55
|
* environment knowledge of its own — a caller that wants automatic
|
|
46
56
|
* detection calls `detectColorLevel` itself and passes the result as
|
|
47
57
|
* `'16'`/`'256'`/`'truecolor'`.
|
|
48
|
-
*
|
|
49
|
-
* `store`/`vault` and `resolveImageSrc`/`resolveHref`/`onDiagnostic` carry
|
|
50
|
-
* the same meaning as `@markii/html`'s identical options.
|
|
51
58
|
*/
|
|
52
|
-
export declare function renderMarkToAnsi(text: string, registry?: AnsiRegistry, store?: AnsiValueStore, vault?: AnsiVaultStore, options?: RenderMarkOptions): string
|
|
59
|
+
export declare function renderMarkToAnsi(text: string, registry?: AnsiRegistry, store?: AnsiValueStore, vault?: AnsiVaultStore, options?: RenderMarkOptions): Promise<string>;
|
|
53
60
|
/**
|
|
54
61
|
* The block-level twin of `renderMarkToAnsi`: renders one already-parsed
|
|
55
|
-
* mdast node OR a whole already-parsed mdast document
|
|
56
|
-
*
|
|
57
|
-
* text. Same registry resolution, same fallbacks, same purity and
|
|
62
|
+
* mdast node OR a whole already-parsed mdast document instead of raw
|
|
63
|
+
* document text. Same registry resolution, same fallbacks, same purity and
|
|
58
64
|
* never-throw guarantees, same options.
|
|
59
65
|
*/
|
|
60
|
-
export declare function renderMarkNodeToAnsi(node: MarkNode | MarkRoot, registry?: AnsiRegistry, store?: AnsiValueStore, vault?: AnsiVaultStore, options?: RenderMarkOptions): string
|
|
66
|
+
export declare function renderMarkNodeToAnsi(node: MarkNode | MarkRoot, registry?: AnsiRegistry, store?: AnsiValueStore, vault?: AnsiVaultStore, options?: RenderMarkOptions): Promise<string>;
|
|
61
67
|
/**
|
|
62
68
|
* Renders `text` as a single, standalone inline directive when that is all
|
|
63
|
-
* it is
|
|
64
|
-
*
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
* it is. Any other source falls back to the exact same rendering
|
|
70
|
+
* `renderMarkToAnsi` would produce.
|
|
71
|
+
*/
|
|
72
|
+
export declare function renderMarkInlineToAnsi(text: string, registry?: AnsiRegistry, store?: AnsiValueStore, vault?: AnsiVaultStore, options?: RenderMarkOptions): Promise<string>;
|
|
73
|
+
/**
|
|
74
|
+
* The live-viewer entry point (phase 3 consumes this): builds the same Ink
|
|
75
|
+
* element tree `renderMarkToAnsi` would render to a string, but returns the
|
|
76
|
+
* element itself so a host (the CLI's `markii view`) can mount it directly
|
|
77
|
+
* with Ink's own `render()` instead of re-rendering a string on every
|
|
78
|
+
* keystroke/resize. `document` accepts raw text OR an already-parsed mdast
|
|
79
|
+
* node/root, mirroring the three string entry points' flexibility.
|
|
67
80
|
*/
|
|
68
|
-
export declare function
|
|
81
|
+
export declare function buildMarkElement(document: string | MarkNode | MarkRoot, registry?: AnsiRegistry, options?: RenderMarkOptions & {
|
|
82
|
+
readonly onExit?: () => void;
|
|
83
|
+
}): ReactElement;
|
|
69
84
|
export {};
|